JavaScript
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
162: The Strategy Pattern
You've probably encountered a situation where you have a function that does three or four different things depending on a flag or a type passed into it. Usually, this starts as a simple if/else block. But as the project grows, that block turns into a monster. That's where the Strategy Pattern comes in. It's essentially a way to swap out the "how" of an operation without changing the "what".
The "Switch" Trap
Let's say we're building a shipping calculator for an e-commerce site. We support a few different carriers, and each has its own pricing logic. I'll start the way I usually do when I'm rushing—by just jamming everything into one function.
const shippingCalculator = (carrier, weight) => {
if (carrier === 'fedex') {
return weight * 1.5 + 10;
} else if (carrier === 'ups') {
return weight * 1.2 + 5;
} else if (carrier === 'usps') {
return weight * 0.8 + 2;
}
throw new Error('Unsupported carrier');
};
This works fine for three carriers. But here is where I usually make the mistake: I assume it'll stay this way. A week later, the business wants to add DHL, then overnight shipping, then international rates. Suddenly, this one function is 200 lines long, and every time I touch it to add a new carrier, I risk breaking the pricing for the existing ones. This violates the "Open/Closed Principle"—our code should be open for extension, but closed for modification.
Defining Our Strategies
Instead of one giant function, I want to treat each shipping method as a "strategy." In JavaScript, we don't need complex class hierarchies for this; simple objects or functions will do. I'll define each algorithm independently.
const fedexStrategy = {
calculate: (weight) => weight * 1.5 + 10
};
const upsStrategy = {
calculate: (weight) => weight * 1.2 + 5
};
const uspsStrategy = {
calculate: (weight) => weight * 0.8 + 2
};
Now, these are isolated. If the FedEx pricing changes, I only touch the fedexStrategy. I don't even have to look at the UPS or USPS code, which means I can't accidentally break them.
Wiring it together with a Context
Now we need a way to use these strategies without the calling code needing to know the details of each calculation. We create a "Context" object. I like to think of the Context as the manager: it doesn't know how to calculate the shipping itself, it just knows who to ask.
class ShippingContext {
constructor(strategy) {
this.strategy = strategy;
}
setStrategy(strategy) {
this.strategy = strategy;
}
calculatePrice(weight) {
return this.strategy.calculate(weight);
}
}
// Let's use it
const weight = 10;
const context = new ShippingContext(fedexStrategy);
console.log(`FedEx: ${context.calculatePrice(weight)}`); // 25
context.setStrategy(upsStrategy);
console.log(`UPS: ${context.calculatePrice(weight)}`); // 17
Notice how the ShippingContext doesn't contain a single if statement. It just calls .calculate(). It doesn't care which strategy it's using, as long as that strategy has a calculate method. This is the core of the pattern: we've decoupled the execution logic from the specific implementation.
If you need to add a new carrier now, you just create a new strategy object and pass it in. You never have to touch the ShippingContext class ever again. That's a huge win for stability in a production codebase.
📋 Practical Task
Build a Dynamic Discount Engine
You are building a checkout system that applies different discount strategies based on the customer's membership level (e.g., Regular, Silver, Gold). Instead of using a switch statement in your checkout function, implement the Strategy Pattern.
- Create three strategy objects:
regularDiscount(0% off),silverDiscount(10% off), andgoldDiscount(20% off). Each should have acalculatemethod that takes a total price and returns the discounted price. - Create a
DiscountContextclass that accepts a strategy in its constructor and has aapplyDiscount(amount)method. - Demonstrate the code by initializing the context with
silverDiscount, calculating the price for $100, and then switching the strategy togoldDiscountto calculate the price for the same amount.
There are no comments for now.