Skip to main content

Strategy Pattern

Define a family of algorithms, encapsulate each one as a function, and swap them at runtime.


The Problem​

You're building an e-commerce checkout. Customers get different discounts: regular users pay full price, VIP members get 20% off, promo codes give 15% off.

The naive approach:

function calculatePrice(price: number, customerType: string): number {
if (customerType === "regular") return price;
if (customerType === "vip") return price * 0.8;
if (customerType === "promo") return price * 0.85;
// ... grows with every new discount type
}

Every new discount = modify this function. It becomes a maintenance nightmare.


The Solution​

Each discount becomes a standalone function. Pick the right one by key at runtime:

import { createStrategies } from "@pithos/core/eidos/strategy/strategy";

const pricing = createStrategies({
regular: (price: number) => price,
vip: (price: number) => price * 0.8,
promo: (price: number) => price * 0.85,
});

// TS infers: "regular" | "vip" | "promo" β€” typos are compile errors
pricing.execute("vip", 100); // 80

New discount? Add one line. No conditionals to update.


Live Demo​

Code
/**
* Pricing strategies using Pithos Strategy pattern.
*
* This is the core of the Strategy pattern:
* each discount rule is a standalone function, selected by key at runtime.
* No interface, no concrete classes, no context β€” just functions in a record.
*/
import { createStrategies } from "@pithos/core/eidos/strategy/strategy";
import type { PricingInput, PricingResult, Product } from "./types";

/** Calculate subtotal from products */
function calculateSubtotal(products: Product[]): number {
return products.reduce((sum, p) => sum + p.basePrice * p.quantity, 0);
}

/** Apply a discount percentage to a subtotal */
function applyDiscount(subtotal: number, percent: number): PricingResult {
const discount = subtotal * (percent / 100);
return {
subtotal,
discount,
total: subtotal - discount,
discountPercent: percent,
};
}

export const pricingStrategies = createStrategies({
/** Regular pricing β€” no discount */
regular: ({ products }: PricingInput) =>
applyDiscount(calculateSubtotal(products), 0),

/** VIP pricing β€” 20% off everything */
vip: ({ products }: PricingInput) =>
applyDiscount(calculateSubtotal(products), 20),

/** Promo pricing β€” 15% off */
promo: ({ products }: PricingInput) =>
applyDiscount(calculateSubtotal(products), 15),

/** Bulk pricing β€” 25% off for orders over $300 */
bulk: ({ products }: PricingInput) => {
const subtotal = calculateSubtotal(products);
return applyDiscount(subtotal, subtotal >= 300 ? 25 : 0);
},
});
Result

Real-World Analogy​

A GPS app with multiple routing options: fastest, shortest, no tolls, scenic. Each is a separate algorithm, but the navigation UI doesn't care which one is active β€” it just calls "calculate route" on the selected strategy.


When to Use It​

  • Multiple algorithms for the same task, selected at runtime
  • You want to add new variants without modifying existing code
  • Complex conditional logic that picks between similar operations

When NOT to Use It​

If you only have two fixed variants, a ternary is simpler. Strategy pays off when algorithms grow over time.


API​