Skip to main content

Simpler Alternatives: Closures, Higher-Order Functions, Composition

What This Concept Is​

Many classical OO patterns were invented in languages where functions were not first-class. When functions are first-class (JavaScript, Python, Kotlin, Scala, Go, Rust, modern Java with lambdas), a pattern often collapses to:

  • a closure (a function that carries state by capture)
  • a higher-order function (a function that takes or returns another function)
  • plain composition (passing one object to another through a constructor)

Classic collapses:

  • Strategy -> pass the algorithm as a function
  • Command -> a thunk or a lambda
  • Observer -> a list of callback functions
  • Template Method -> a function that takes the varying steps as parameters
  • Factory Method -> a function that returns the right value
  • Iterator -> a language feature (for ... of, yield, Iterator)
  • small Decorators -> a wrapper function that returns a new function

The point is not "OO is obsolete." The point is that in languages with first-class functions, the minimum viable design is often a function, and the class-based pattern is worth adding only when a function is genuinely insufficient.

Why It Matters Here​

Many "design pattern" conversations are symptoms of a mismatch between a language feature and a solution shape. You can save a reader dozens of lines of boilerplate -- and dozens of minutes of cognitive load -- by choosing the smaller shape when both work.

This concept teaches you to identify those collapse cases and to defend the richer class-based form when it genuinely earns its cost.

Concrete Example​

Pattern-heavy version (OO Strategy):

interface DiscountStrategy { int apply(int cents); }
class LoyaltyDiscount implements DiscountStrategy { public int apply(int c){ return (int)(c*0.9); } }
class EmployeeDiscount implements DiscountStrategy { public int apply(int c){ return (int)(c*0.8); } }
int price(Cart cart, DiscountStrategy s) { return s.apply(cart.subtotal()); }

Closure version:

const loyalty  = cents => Math.round(cents * 0.9);
const employee = cents => Math.round(cents * 0.8);
function price(cart, discount) { return discount(cart.subtotal()); }

They encode the same design. The closure version:

  • has no named type
  • cannot be extended by subclass
  • cannot be introspected by name

If none of those losses matter, the closure wins on readability. If you need pluggable policies with names, serialization, or multi-method contracts, the class form earns its cost.

A second example: a Decorator as a higher-order function.

const withRetry = (fn, n = 3) => async (...args) => {
let lastErr;
for (let i = 0; i < n; i++) {
try { return await fn(...args); }
catch (e) { lastErr = e; }
}
throw lastErr;
};

const robustFetch = withRetry(fetch, 5);

That is a Decorator. No classes, no interfaces, no boilerplate.

Common Confusion / Misconception​

  • "Functional" is not the opposite of "object-oriented." Most modern codebases mix both fluently.
  • A closure is not inherently simpler than a class. It is simpler when the class would have been a one-method shell; it is not simpler when multiple related methods, naming, or inheritance matter.
  • Collapsing a pattern to a function can lose useful things: names in stack traces, type checking in some languages, discoverability. Decide consciously.
  • Not every language supports every collapse cleanly. Java lambdas are more constrained than Python closures; type systems differ; pick the tool your team actually uses.

How To Use It​

When you feel the urge to introduce a pattern, ask:

  1. Does the pattern have exactly one method? -> consider a function.
  2. Does the pattern only store one piece of state? -> consider a closure.
  3. Is "decorating" the behavior the only reason for the class? -> consider a higher-order function.
  4. Is inheritance doing real work, or just being used for code reuse? -> consider composition + delegation instead.
  5. If you still want the pattern, write both; keep the simpler one unless the richer one names something the team actually needs.

Check Yourself​

  1. What does the class-based form give you that a closure does not?
  2. Why does "Template Method" often collapse to a function that takes callbacks?
  3. When is composing small functions worse than one honest class?

Mini Drill or Application​

Take three real patterns from this module (say, Strategy, Decorator, and small Factory). Do two things per pattern:

  1. Write the pattern in both styles: class-based and function-based.
  2. Commit to one choice for a hypothetical team and write two sentences justifying it in terms of your language, team size, and the specific change pressure.

Read This Only If Stuck​