Beyond Interview LeetCode: Real Production Design Patterns in TypeScript
In tech interviews, candidates are often evaluated on whether they can invert a binary tree in O(n) time or find the median of two sorted arrays. But once you start building production software at an agency or enterprise, 95% of your time is spent wrestling with:
- Growing 800-line
if/elsestatements calculating dynamic pricing rules. - Tightly coupled third-party vendor SDKs (Razorpay, Stripe, WhatsApp Cloud API).
- Memory leaks caused by unhandled event subscriptions.
- Serverless connection pool exhaustion on high-concurrency spikes.
Here is how we use classic Gang of Four (GoF) Design Patterns modernized in TypeScript to engineer resilient systems at BirdMint.
Pattern 1: The Strategy Pattern (Killing Monolithic Switch Statements)
The Anti-Pattern
function calculateShipping(orderType: string, weight: number): number {
if (orderType === "standard") {
return weight * 5;
} else if (orderType === "express") {
return weight * 15 + 20;
} else if (orderType === "international") {
return weight * 45 + 100;
}
throw new Error("Unknown shipping type");
}
Every time business introduces a new shipping tier, you are modifying a core file, risking regressions across existing checkout logic.
The Strategy Architecture
export interface ShippingStrategy {
calculate(weight: number): number;
}
export class ExpressShipping implements ShippingStrategy {
calculate(weight: number) {
return weight * 15 + 20;
}
}
export class InternationalShipping implements ShippingStrategy {
calculate(weight: number) {
return weight * 45 + 100;
}
}
export class CheckoutCalculator {
constructor(private strategy: ShippingStrategy) {}
getCost(weight: number) {
return this.strategy.calculate(weight);
}
}
Now, adding a new rule is purely additive: create a new class adhering to ShippingStrategy, with zero edits to existing production classes.
Pattern 2: The Factory Pattern for Vendor Neutrality
When building payment processing systems, never hardcode new RazorpayClient() directly inside your controllers. If tomorrow you expand internationally and need Stripe or PayPal, your controller code is completely compromised.
export interface IPaymentGateway {
charge(amount: number, currency: string): Promise<{ transactionId: string }>;
}
export class PaymentGatewayFactory {
static getGateway(region: "IN" | "GLOBAL"): IPaymentGateway {
if (region === "IN") return new RazorpayGateway();
return new StripeGateway();
}
}
The rest of your application code interacts solely with IPaymentGateway. Vendor changes become a one-line configuration update.
Key Takeaway
Design patterns are not academic dogma. They are communication tools and structural blueprints that prevent your codebase from decaying into an unmaintainable legacy monolith.
