Suggest an editImprove this articleRefine the answer for “What is Strategy design pattern?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Strategy design pattern** defines a family of algorithms, wraps each as a separate object, and lets a context object swap between them at runtime without changing its own code. ```javascript const p = new Processor(new PayPalStrategy()); p.process(100); // "Paid $100 via PayPal" p.strategy = new CardStrategy(); p.process(100); // "Paid $100 via Card" ``` **Key point:** behavior changes by swapping strategy objects, not by rewriting conditionals.Shown above the full answer for quick recall.Answer (EN)Image**Strategy design pattern** - a behavioral pattern that defines a family of algorithms, wraps each in its own object, and lets the context swap between them at runtime. ## Theory ### TL;DR - Analogy: swapping engines at a pit stop - same car (context), different engine (strategy) for track, highway, or off-road - Main idea: replace if/else chains by delegating behavior to injected objects - Use when: 3+ algorithms for the same task, or user picks one at runtime - Skip when: 2 options only. A ternary or two direct function calls is enough. ### Quick example ```javascript class PayPalStrategy { pay(amount) { return `Paid $${amount} via PayPal`; } } class CardStrategy { pay(amount) { return `Paid $${amount} via Card`; } } class Processor { constructor(strategy) { this.strategy = strategy; } process(amount) { return this.strategy.pay(amount); } } const p = new Processor(new PayPalStrategy()); console.log(p.process(100)); // "Paid $100 via PayPal" p.strategy = new CardStrategy(); console.log(p.process(100)); // "Paid $100 via Card" ``` `Processor` calls `pay()` and trusts the strategy to handle the rest. It never checks which payment method you passed in. ### Key difference Without Strategy, `process()` grows an `if/else` for every payment method. Add Crypto - open the file, edit the block, risk breaking existing behavior. With Strategy, you add one class and inject it. The context never changes. Algorithms evolve independently of the code that runs them. ### When to use - 3+ ways to do the same operation (sort, validate, compress, authenticate): Strategy - User picks a behavior at runtime (payment method, login provider): Strategy - Behavior changes based on config or environment: Strategy - You have a `switch` with 5+ cases all doing "the same kind of thing": refactor to Strategy - 2 options, no runtime switching: use a ternary or two direct function calls ### How JavaScript handles this In V8, strategy objects are first-class values. When `this.strategy.pay()` runs, the engine resolves it via a dynamic property lookup through the prototype chain at call time. No compile-time binding, no vtable like in C++. Any object with a `pay()` method works as a valid strategy. Duck typing does the job that formal interfaces do in Java or C#. No `implements` keyword needed. ### Common mistakes **Forgetting to validate the strategy in the constructor** ```javascript const p = new Processor(); // no strategy passed p.process(100); // TypeError: Cannot read properties of undefined ``` Guard it early: ```javascript constructor(strategy) { if (!strategy?.pay) throw new Error('Strategy must implement pay()'); this.strategy = strategy; } ``` **Sharing stateful strategies across contexts** ```javascript class LoggingStrategy { constructor() { this.log = []; } pay(amount) { this.log.push(amount); return 'Paid'; } } const shared = new LoggingStrategy(); const p1 = new Processor(shared); const p2 = new Processor(shared); p1.process(100); p2.process(200); // shared.log is [100, 200] - state leaked between both processors ``` Strategies should be stateless. If you need state, create a fresh instance per context. This is a common source of subtle bugs in logging and analytics strategies. **Over-engineering for two options** ```javascript // Unnecessary: two classes for a binary choice class USDStrategy { pay() { /* ... */ } } class EURStrategy { pay() { /* ... */ } } ``` Two options? Write a ternary. Wait for 3+ real runtime cases before reaching for Strategy. **No null check after dynamic swaps** ```javascript p.strategy = null; p.process(100); // crash ``` If strategies get swapped at runtime, guard against null: ```javascript process(amount) { return this.strategy?.pay(amount) ?? 'No strategy configured'; } ``` ### Real-world usage - **Passport.js:** `passport.use(new LocalStrategy(...))` and `passport.use(new GoogleStrategy(...))` - every auth provider is a Strategy plugged into the same middleware context. Every codebase I've seen using Passport already has this pattern in it, whether the team named it or not. - **TanStack Table v8+:** comparator functions injected via column meta for runtime sort switching - **Node.js zlib:** `createDeflate()`, `createGzip()`, `createBrotliCompress()` - each is a distinct compression strategy in a stream pipeline - **Lodash:** `_.sortBy` with iteratee functions acts as lightweight Strategy without class overhead ### Follow-up questions **Q:** How is Strategy different from Template Method? **A:** Template Method puts the algorithm skeleton in a base class and lets subclasses override specific steps via inheritance. Strategy replaces the whole algorithm via composition. You swap behavior without creating a subclass. **Q:** Strategy vs Factory - what is the difference? **A:** Factory creates objects. Strategy selects which algorithm runs at runtime. They often appear together: a factory can instantiate the right strategy based on config or user input. **Q:** When does Strategy add unnecessary overhead? **A:** When you have 2 options and no runtime swap. For performance-critical code, extra object allocation and method indirection add cost. In those cases, pass a function directly. `Array.sort(comparatorFn)` does exactly this without wrapping anything in a class. **Q:** How would you refactor an if/else chain to Strategy? **A:** Extract each branch into a class with a consistent method name like `execute()`. Replace the conditional block with a context that receives the strategy via constructor. Adding a new case means adding a new class, not editing existing logic. **Q:** How does Strategy work in functional programming? **A:** Higher-order functions replace classes. `const sorter = (strategy) => (items) => [...items].sort(strategy)` - pass a comparator, get a sorter. Same concept, less ceremony. **Q (senior):** In a microservices architecture, how would you manage strategies distributed across services? **A:** Use a registry pattern combined with service discovery. The context fetches the right strategy via an API call (REST or gRPC). Add circuit breakers for the remote fetch. If the strategy service is down, fall back to a local default. This is how feature flag systems and A/B testing platforms handle distributed strategy selection at scale. ## Examples ### Basic: Swappable payment strategies ```javascript class PayPalStrategy { pay(amount) { return `Paid $${amount} via PayPal`; } } class CreditCardStrategy { pay(amount) { return `Paid $${amount} via Credit Card`; } } class CryptoStrategy { pay(amount) { return `Paid $${amount} via Crypto`; } } class PaymentProcessor { constructor(strategy) { if (!strategy?.pay) throw new Error('Invalid strategy'); this.strategy = strategy; } setStrategy(strategy) { this.strategy = strategy; } process(amount) { return this.strategy.pay(amount); } } const processor = new PaymentProcessor(new PayPalStrategy()); console.log(processor.process(50)); // "Paid $50 via PayPal" processor.setStrategy(new CryptoStrategy()); console.log(processor.process(50)); // "Paid $50 via Crypto" ``` Three payment methods, zero conditionals in `PaymentProcessor`. Adding a fourth means writing one new class. Nothing else changes. ### Intermediate: Express auth middleware with pluggable strategies ```javascript class EmailStrategy { async auth(req) { const { email, password } = req.body; // Simulate DB lookup return email === 'user@example.com' && password === 'secret'; } } class OAuthStrategy { async auth(req) { const { token } = req.body; // Simulate token validation return token === 'valid-oauth-token'; } } const authMiddleware = (strategy) => async (req, res, next) => { const isValid = await strategy.auth(req); if (isValid) return next(); res.status(401).send('Unauthorized'); }; app.post('/login', authMiddleware(new EmailStrategy())); app.post('/oauth/callback', authMiddleware(new OAuthStrategy())); ``` This is how Passport.js works at its core. The middleware calls `auth()` and either forwards or rejects. Adding a new provider - SAML, API key, magic link - means adding a class, not touching the middleware function. ### Advanced: Sortable React table with runtime strategy switching ```javascript const useSortableData = (items, initialConfig = null) => { const [sortConfig, setSortConfig] = useState(initialConfig); const comparators = { name: (a, b) => a.name.localeCompare(b.name), age: (a, b) => a.age - b.age, // Secondary sort: score desc, then name asc on tie score: (a, b) => b.score - a.score || a.name.localeCompare(b.name), }; const sortedItems = useMemo(() => { if (!sortConfig?.key || !comparators[sortConfig.key]) return items; return [...items].sort(comparators[sortConfig.key]); // spread prevents mutation }, [items, sortConfig]); return { items: sortedItems, sortConfig, setSortConfig }; }; // In the component: // <button onClick={() => setSortConfig({ key: 'score' })}>Sort by score</button> ``` Two things worth noting here. First, `[...items]` before sort - `Array.sort()` mutates the original array, which breaks React's immutability expectations. Without the spread, every sort call modifies the source list in place. Second, the `score` comparator uses a secondary sort: score descending, then name alphabetically when scores tie. This kind of multi-field logic is where Strategy pays off. You name it, test it in isolation, and swap it without touching the component.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.