All chapters in this track
- 01Chapter 1: Why Patterns Exist — Vocabulary, Not a Checklist
- 02Chapter 2: Patterns and SOLID: How They Relate
- 03Chapter 3: Creational Patterns I: Factory Method & Abstract Factory
- 04Chapter 4: Creational Patterns II: Builder, Singleton, Prototype
- 05Chapter 5: Structural Patterns I: Adapter & Facade
- 06Chapter 6: Structural Patterns II: Decorator & Proxy
- 07Chapter 7: Structural Patterns III: Composite, Bridge & Flyweight
- 08Chapter 8: Behavioral Patterns I: Strategy & Template Method
- 09Chapter 9: Behavioral Patterns II: Observer & Mediator
- 10Chapter 10: Behavioral Patterns III: Command & Memento
- 11Chapter 11: Behavioral Patterns IV: State & Chain of Responsibility
- 12Chapter 12: Behavioral Patterns V: Iterator & Visitor
- 13Chapter 13: Anti-Patterns and Overuse: Recognizing "Pattern Fever"
- 14Chapter 14: How Dynamic and Functional Languages Solve These Problems Differently
- 15Chapter 15: Architectural Patterns I: MVC/MVVM & Layered/Hexagonal Architecture
- 16Chapter 16: Architectural Patterns II: Event-Driven Architecture & CQRS
- 17Chapter 17: Architectural Patterns III: The Repository Pattern
- 18Chapter 18: Recognizing Which Pattern Fits an Interview Design Prompt
Chapter 14 of 18
Chapter 14: How Dynamic and Functional Languages Solve These Problems Differently
Why this matters
Many classic patterns exist specifically to work around limitations of statically-typed, class-based languages from the era the Gang of Four catalog was written in (primarily C++ and Smalltalk-influenced designs, formalized around Java-style OOP soon after). Dynamic typing and first-class functions — both present in Python, and both central to TypeScript/JavaScript's functional side — make several patterns' entire ceremony unnecessary. Recognizing which patterns fall into this category, and why, sharpens your understanding of what each pattern is really compensating for.
Strategy, without the interface
[BEGINNER] In a language with first-class functions, "Strategy" often needs nothing more than passing a function directly — the interface-plus-classes ceremony exists specifically to give statically-typed, class-only languages a way to treat "a swappable behavior" as a value:
// The full Strategy pattern from Chapter 8, condensed to a function type — no interface, no classes:
type CompressionStrategy = (data: string) => string;
function gzipCompress(data: string): string { return `gzip(${data})`; }
function zipCompress(data: string): string { return `zip(${data})`; }
class FileArchiver {
constructor(private strategy: CompressionStrategy) {}
archive(data: string): string { return this.strategy(data); }
}
new FileArchiver(gzipCompress).archive("file contents"); // "gzip(file contents)" — same behavior, no class hierarchy
from typing import Callable
CompressionStrategy = Callable[[str], str]
def gzip_compress(data: str) -> str:
return f"gzip({data})"
class FileArchiver:
def __init__(self, strategy: CompressionStrategy) -> None:
self._strategy = strategy
def archive(self, data: str) -> str:
return self._strategy(data)
FileArchiver(gzip_compress).archive("file contents")
Every place in this book that Strategy created an interface with a single method is a candidate for this simplification — a single-method interface is, structurally, just a function with a name.
Decorator, without inheritance
Chapter 6's Decorator pattern used an abstract base class and interface implementations to compose behavior. In a language with first-class functions, function composition achieves the same combinability directly:
type Beverage = { cost: number; description: string };
const coffee = (): Beverage => ({ cost: 200, description: "Coffee" });
const withMilk = (bev: Beverage): Beverage => ({ cost: bev.cost + 50, description: `${bev.description} + Milk` });
const withSugar = (bev: Beverage): Beverage => ({ cost: bev.cost + 25, description: `${bev.description} + Sugar` });
const order = withSugar(withMilk(coffee())); // the same combinability, as plain function composition
console.log(order.description, order.cost); // "Coffee + Milk + Sugar" 275
from dataclasses import dataclass, replace
@dataclass
class Beverage:
cost: int
description: str
def with_milk(bev: Beverage) -> Beverage:
return replace(bev, cost=bev.cost + 50, description=f"{bev.description} + Milk")
order = with_milk(Beverage(cost=200, description="Coffee"))
Observer, as a built-in language/library feature
Chapter 9's Observer pattern is so common that most modern languages and frameworks provide it as a built-in primitive rather than something you hand-roll: Python's asyncio event mechanisms, JavaScript's native EventTarget, and reactive libraries in both ecosystems all implement Observer's shape under a different, more specific name — subscribing to change notifications without either side needing to define a custom Observer interface from scratch.
Command, as a first-class function
Chapter 10's Command pattern turned an action into an object specifically so it could be stored, passed, and invoked uniformly. In a language with functions as values, a plain function (or closure, capturing whatever state it needs) already is a storable, passable, invocable action:
type Command = () => void;
const commands: Command[] = [];
commands.push(() => console.log("Insert text"));
commands.push(() => console.log("Delete text"));
commands.forEach(cmd => cmd()); // uniform invocation — no Command interface needed
[SENIOR INSIGHT] The undo-supporting version of Command (from Chapter 10) is the interesting exception: representing both execute and undo together genuinely benefits from a small object/tuple (or a pair of closures) even in a functional style — the simplification above works cleanly for the simple "invoke later" case, but Command's richer use cases (undo, serialization for logging, introspection) still often want more structure than a bare function provides. This is a useful general lesson: the more a pattern's payoff depends on structure beyond "just calling a function," the less a first-class-function language can simplify it away.
Which patterns don't simplify much
[SENIOR INSIGHT] Not every pattern in this book shrinks meaningfully in a dynamic or functional-leaning language:
| Pattern | Simplifies significantly? | Why |
|---|---|---|
| Strategy, Command (simple case) | Yes | The entire "interface" was one method — a function replaces it directly |
| Decorator | Yes, for stateless composition | Function composition directly replaces the wrapper-class chain |
| Observer | Often, via built-in/library primitives | The shape is common enough to be a language/library feature already |
| Singleton | Partially | Python's module system already gives you "load once, import everywhere" for free, without a class-based Singleton at all — modules are singletons by construction |
| Abstract Factory, Bridge | Not much | These solve structural problems (family consistency, independent axes of variation) that exist regardless of whether functions are first-class |
| Visitor | Not much | Its core trade-off (types vs. operations extensibility) is about a type hierarchy's shape, orthogonal to whether the language has first-class functions |
| Composite, State | Not much | These model genuine structural/behavioral shapes (trees, state machines) that aren't primarily about working around class-based ceremony |
Common mistakes
- Assuming every pattern "goes away" in Python or with functional-style TypeScript. Only the patterns whose ceremony was specifically about giving a behavior first-class status (Strategy, simple Command, Decorator) simplify significantly — structural patterns solving genuine structural problems (Abstract Factory, Bridge, Composite, Visitor) don't shrink much regardless of language.
- Over-using bare functions for cases that genuinely need more structure, like Command implementations that need undo, logging, or introspection — forcing these into plain closures can lose capabilities a small object would provide more clearly.
- Missing that Python's module system already gives Singleton-like behavior for free (a module is loaded once and shared on every import), and building a class-based Singleton unnecessarily on top of that existing guarantee.
- Not recognizing Observer's shape in a framework's built-in event/reactive primitives, and hand-rolling a custom Observer implementation when the platform or a library already provides one, well-tested and integrated.
Checklist: choosing between a classic pattern implementation and a language-native simplification
- If a pattern's interface has exactly one method, consider a plain function or closure instead of an interface + classes (Strategy, simple Command).
- If Decorator's composition is purely about combining stateless transformations, consider function composition over a class hierarchy.
- Before hand-rolling Observer, check whether the language or an existing library already provides an event/subscription primitive that fits.
- In Python specifically, before reaching for class-based Singleton, consider whether a plain module already provides the "load once, share everywhere" guarantee you need.
- Don't force structural patterns (Abstract Factory, Bridge, Composite, Visitor) into a "functional" simplification just for consistency — their value is orthogonal to whether functions are first-class, so keep them in their natural, class-based shape when the underlying problem genuinely calls for them.
Discussion / interview questions
-
"Why does the Strategy pattern often need less ceremony in Python or functional-style TypeScript than in Java?" Strong answer: Strategy's "interface" is typically a single method; in a language with first-class functions, a plain function already serves as a swappable behavior value, making the interface-plus-classes ceremony optional rather than necessary.
-
"Which classic patterns don't meaningfully simplify in a dynamic or functional-leaning language, and why?" Strong answer: Structural/family-consistency patterns like Abstract Factory and Bridge, and shape-driven patterns like Composite and Visitor — their complexity comes from the actual structural problem (family consistency, independent axes of variation, tree traversal, type-vs-operation trade-offs), not from working around a lack of first-class functions.
-
"How does Python's module system relate to the Singleton pattern?" Strong answer: A Python module is loaded once per process and shared across every import, which already provides Singleton's core guarantee ("exactly one instance, accessible everywhere") without needing a class-based Singleton implementation at all, for many practical cases.
-
"When would a 'simplified' functional version of Command lose something the classic object-based version provides?" Strong answer: When undo, logging/serialization, or introspection of the action is needed — a bare function can be invoked uniformly but can't easily carry a paired
undooperation or be inspected/logged the way a small Command object naturally can.
Chapter summary / cheat sheet
- Patterns whose ceremony exists mainly to give a behavior first-class status (Strategy, simple Command, stateless Decorator) simplify significantly in languages with first-class functions — often to a bare function or closure.
- Patterns solving genuine structural problems (Abstract Factory, Bridge, Composite, Visitor) don't simplify much regardless of language, because their complexity isn't about the lack of first-class functions.
- Observer is frequently already provided as a language or library primitive (events, reactive subscriptions) rather than needing to be hand-rolled.
- Python's module system already provides Singleton's core guarantee for many cases, without a class-based implementation.
- The deeper lesson: recognizing why a pattern needs the structure it has tells you whether a language feature can replace that structure, or whether the structure reflects the actual problem and would be needed in any language.