SuperAPI AI Directory

implementation-functional-patterns

Coding & development

View repository

Updated 10-16 03:46

About this skill

TypeScript Functional Patterns

Implementation reference for the functional shapes that supersede the Gang of Four catalog in idiomatic TypeScript. Sibling to implementation-design-patterns: read that one when the answer is a class, this one when the answer is a function, a tagged union, or a small data structure.

TypeScript has first-class functions, discriminated unions, structural typing, and zero ceremony around closures. That means the 22 GoF patterns — written in the catalog as class hierarchies because the source material targets Java/C# — collapse to far fewer functional shapes in real TS code. Several patterns share a functional answer: tagged unions cover State, Visitor, and Composite; factory functions cover Factory Method, Abstract Factory, Prototype, and Memento; event emitters cover Mediator and Observer; wrapper functions cover Adapter and Facade. This skill names those collapses, the placement rules they imply, and the performance trade-offs.

When to Apply

  • Refactoring a Factory class hierarchy, an AbstractFactory returning families of products, or a class with clone() / Memento save-restore
  • Refactoring a Singleton class with private constructor + static getInstance
  • Refactoring a mutable Builder class for a configuration object with many optional fields
  • Refactoring a Strategy / Template Method / Bridge class hierarchy where variation is a single method
  • Replacing a Chain of Responsibility class chain or Decorator wrapper-class stack
  • Replacing a custom Iterator class with stream methods, generators, or lazy iterator helpers
  • Replacing a Command class with a closure stored in a queue (when undo/serialization is not required)
  • Replacing an Adapter / Facade class that exists only to forward calls or hide subsystem orchestration
  • Replacing a Proxy class with native JS Proxy (for transparent interception) or an HOF wrapper (for selective wrapping)
  • Replacing a Flyweight factory class with a Map/WeakMap cache + factory function
  • Replacing a State / Visitor / Composite class hierarchy with a discriminated union + exhaustive match function
  • Replacing a Mediator / Observer class with an event emitter or reactive signal
  • Reviewing TSX where lambdas appear inline in JSX, hook deps, or memo'd child props — placement controls identity
  • Recognizing imperative for loops with mutated accumulators that would read more honestly as reduce, Object.groupBy, or flatMap

Rule Categories

# Category Impact Rules Theme
1 Creational alternatives (create) HIGH 3 Factory functions, module-scope singletons, fluent immutable builders
2 Higher-order functions (hof) HIGH 1 Pass a function instead of a class
3 Pipelines & composition (pipe) HIGH 2 Compose small functions: pipe (data flow), compose (wrapper layering)
4 Stream methods (stream) HIGH 4 map/filter/flatMap/reduce, lazy iteration, single-pass chains
5 Wrappers (wrap) HIGH 2 Wrapper functions for Adapter/Facade; native Proxy or HOF for Proxy
6 Caching & sharing (cache) HIGH 1 Map/WeakMap + factory function over Flyweight class
7 Pattern matching (match) HIGH 1 Discriminated unions + exhaustive match for State/Visitor/Composite
8 Signals & event emitters (signal) HIGH 1 Event emitter / signal for Mediator/Observer
9 Placement & identity (place) HIGH 1 Where the lambda lives controls behavior
10 Closures as data (closure) MEDIUM 1 Functions that carry their state

17 rules across 10 categories, covering all 22 GoF patterns (some patterns share a functional answer — see the GoF → Functional Map below).

GoF → Functional Map

The full mapping from each Gang of Four pattern to its functional answer in idiomatic TS. Read this table to find the rule for a specific pattern; read the categories above to find rules by functional technique.

GoF Group GoF Pattern Functional Equivalent Rule
Creational Factory Method Function returning tagged object create-factory-function-over-factory-classes
Abstract Factory Function returning record of constructors covered by factory-function rule
Builder Object literal + Partial<T>, or fluent immutable create-fluent-immutable-builder
Prototype structuredClone / spread / Immer produce covered by factory-function rule
Singleton Module-scope const, lazy ??= memo create-module-scope-over-singleton
Structural Adapter Wrapper function translating shape wrap-function-over-adapter-and-facade
Bridge HOF parametrized by implementation covered by hof-lambda-as-strategy
Composite Discriminated union + recursive fn covered by tagged-union rule
Decorator compose(withA, withB, withC)(target) pipe-compose-over-decorator
Facade Single high-level function hiding subsystem covered by wrap-function rule
Flyweight Map/WeakMap + factory function cache-weakmap-over-flyweight
Proxy Native Proxy or HOF wrapper wrap-proxy-native-or-hof
Behavioral Chain of Responsibility pipe(fn1, fn2) / array fold pipe-pipeline-over-chain-of-responsibility
Command Closure () => void closure-as-command
Iterator Stream methods + generators stream-flatmap-over-nested-loops, stream-reduce-over-imperative-accumulation, stream-lazy-iteration-for-large-or-infinite, stream-prefer-single-pass-over-chained-passes
Mediator Event emitter / signal signal-event-emitter-over-mediator-and-observer
Memento Immutable snapshot via structuredClone covered by factory-function rule
Observer Event emitter / signal / RxJS covered by event-emitter rule
State Discriminated union + exhaustive match match-tagged-union-over-state-visitor-composite
Strategy Lambda hof-lambda-as-strategy
Template Method HOF taking step callback covered by hof-lambda rule
Visitor Discriminated union + match function covered by tagged-union rule

How to Use

  1. Find the pattern. If you know which GoF pattern you'd reach for, look it up in the GoF → Functional Map above and read the linked rule. If you only know the symptom (loop with accumulator, class returning class, three setters), the Quick Reference below groups rules by functional technique.
  2. Read "When NOT to apply". Every rule lists the narrow conditions where the class form still wins. The skill is a complement to the parent skill, not a repudiation — keep the class when serialization, runtime introspection, typed inter-pattern relations, cross-cutting state, framework integration, or lifecycle ownership demands it.
  3. Check identity assumptions in TSX. If the code lives in a TSX file or runs inside a hook, also read the place-* rules — placement decides whether memo, useEffect, and React Compiler can do their jobs.
  4. Mind the performance. Every rule has a ### Performance trade-offs section quantifying time, memory, and allocation costs. Most functional forms are performance-equivalent to the class form; a few (chained streams, flatMap) have real constant-factor costs that matter in hot paths.

Quick Reference

1. Creational alternatives

  • create-factory-function-over-factory-classes — Function returning a tagged object instead of a Factory class hierarchy. Covers Factory Method, Abstract Factory, Prototype, and Memento. "I'd write new FooFactory().create() — but a function returning the tagged object is shorter and tree-shakes." — HIGH
  • create-module-scope-over-singleton — export const x = … or lazy ??= instead of class X { private static instance; getInstance() }. ES modules ARE singletons; the class form is anti-idiom in TS. "I need exactly one of these — config, DB client, logger." — HIGH
  • create-fluent-immutable-builder — Object literal + Partial<T> for simple cases; fluent immutable (each method returns a new builder) for type-state-tracked DSLs. "My constructor has 10 parameters / my Builder class has 8 setters." — HIGH

2. Higher-order functions

  • hof-lambda-as-strategy — Pass a comparator/predicate/transformer lambda instead of defining a Strategy class. Also covers Template Method (HOF with step callback) and Bridge (HOF parametrized by implementation). "My Strategy interface has one method." — HIGH

3. Pipelines & composition

  • pipe-pipeline-over-chain-of-responsibility — pipe(validate, authorize, parse)(req) or an array fold of handlers, instead of a linked list of Handler classes. "My CoR chain handlers each do one transform and pass the result along." — HIGH
  • pipe-compose-over-decorator — compose(withLogging, withCache, withAuth)(handler) — each wrapper is (handler) => handler, not a Decorator class. Right-to-left order reads top-down like the class stack. "I want to add logging + caching + auth around this handler." — HIGH

4. Stream methods

  • stream-flatmap-over-nested-loops — .flatMap for one-to-many transforms instead of for + push or map().reduce(concat). "For each user, expand to all their orders, then collect." — HIGH
  • stream-reduce-over-imperative-accumulation — reduce / Object.groupBy / Map.groupBy instead of let acc = …; for (…) acc[…] = …. The most common functional pattern in real TS: sums, counts, indexes, histograms. "I'm building up a total / index / grouped map in a loop." — HIGH
  • stream-lazy-iteration-for-large-or-infinite — Generators or TC39 Iterator helpers (Iterator.from(arr).filter(p).take(10).toArray()) when only a prefix of results is needed. O(matched-needed) instead of O(n). "First N matches from a huge or unbounded source." — HIGH
  • stream-prefer-single-pass-over-chained-passes — Collapse .filter().map().filter() into one reduce or for-of when the input is large or the chain is hot. Three passes → one; halves peak memory; 2–5× faster in measured hot paths. "This chain runs on every request / render over thousands of items." — HIGH

5. Wrappers

  • wrap-function-over-adapter-and-facade — Wrapper function translating one interface to another (Adapter) or hiding subsystem orchestration (Facade). "I'd write class XAdapter implements Y whose every method is one-line forwarding." — HIGH
  • wrap-proxy-native-or-hof — Native Proxy for transparent dynamic-key interception, HOF wrapper for selective method-level wrapping. "I want lazy loading / access control / interception without a Proxy class." — HIGH

6. Caching & sharing

  • cache-weakmap-over-flyweight — WeakMap/Map cache + factory function instead of Flyweight factory class. WeakMap auto-cleans when keys go out of scope. "I'm allocating millions of similar objects with shared state." — HIGH

7. Pattern matching

  • match-tagged-union-over-state-visitor-composite — Discriminated union + switch on tag + assertNever — covers State (class per state), Visitor (double dispatch), and Composite (Leaf/Branch). Probably the single highest-payoff functional pattern in TS. "My class has a giant switch on kind / I have N state classes / I'd write a Visitor over my AST." — HIGH

8. Signals & event emitters

  • signal-event-emitter-over-mediator-and-observer — Event emitter (mitt, EventEmitter) or reactive signal (Solid, Preact signals, Zustand) instead of Subject/Observer classes or central Mediator class. "Many objects need to react when one changes / form fields need to coordinate / cross-component events." — HIGH

9. Placement & identity

  • place-module-scope-pure-transformers — Put pure transformer lambdas at module scope (stable identity, reusable, tree-shakable). Nest them inside a function only when they capture something. "This (s) => s.toLowerCase() doesn't need to be in the component body." — HIGH

10. Closures as data

  • closure-as-command — Store a () => void closure in the queue/history/callback list instead of a Command class with execute(). "I need a queue of deferred operations and never need to inspect or serialize them." — MEDIUM

How to Choose: Class vs Function

The class form (see implementation-design-patterns) earns its overhead when at least one of these is true:

  • Serialization — Commands or Mementos that must survive a process restart or cross a wire
  • Runtime registry / introspection — the system enumerates known strategies, displays them in a picker, or attaches metadata
  • Typed inter-pattern relations — Visitor over an AST where node types reference each other, State machine where states reference each other, Mediator with typed roles
  • Cross-cutting state — the "variation" carries its own configuration, lifecycle, or invariants beyond the single call
  • Stable identity for instanceof — exhaustive matching on a finite set of named classes (rare; discriminated unions usually win)
  • Lifecycle ownership — the object owns a resource (connection, file handle, disposable) and using / Symbol.dispose integration matters
  • Framework integration — ORMs, DI containers, decorator-based libraries, RxJS class-based services expect classes

Otherwise, default to the function (or tagged union, or data structure). The class wraps the value in ceremony that earns nothing.

References

  1. MDN — Array.prototype
  2. TC39 — Iterator Helpers proposal
  3. Mostly Adequate Guide to Functional Programming (Brian Lonsdorf)
  4. TC39 — Pipeline Operator proposal
  5. MDN — Closures
  6. TS Handbook — Discriminated Unions
  7. MDN — Proxy
  8. MDN — WeakMap
  9. MDN — structuredClone