Software Architecture

Which Design Patterns Still Matter — and Which Your Language Already Absorbed

Peter Norvig said 16 of 23 GoF design patterns vanish in a dynamic language. We rewrote all 23 in 7 languages to check, and counted 14. This guide gives the verdict pattern by pattern — the 4 your language already ships, the 10 that collapse into a single function, and the 9 you still have to write by hand — plus why those 9 survived, what changes when you switch language, and the three comparisons interviewers actually ask about.

Mohammed Yaseen
Mohammed Yaseen
Last Updated: · 11 min read
ShareXLinkedIn
Which Design Patterns Still Matter — and Which Your Language Already Absorbed

Quick Answer: Design patterns are still relevant as vocabulary, but roughly 60% of the original implementations are not. Writing all 23 Gang-of-Four patterns in seven languages, we found 4 are shipped by the language itself (Iterator, Strategy, Flyweight, Proxy), 10 collapse from a class hierarchy into a function or a module, and 9 still need essentially the structure the 1994 book describes. The 9 survivors are the ones that solve design problems; the 14 that faded were compensating for languages that could not pass behaviour around.

Every discussion of this question eventually quotes the same line: Peter Norvig's finding that 16 of the 23 patterns become "invisible or simpler" in a dynamic language. It comes from a 1996 talk, Design Patterns in Dynamic Languages, and it is usually deployed as a conversation-ender.

It is also thirty years old, and it was about Lisp and Dylan — not about the languages anyone reading this ships on Monday. Nobody seems to have re-run it.

So we did. We implemented all 23 patterns in Java, Python, C++, C#, JavaScript, Go and TypeScript — 161 code samples — for the interactive Design Patterns handbook we just published. This article is what that exercise taught us: which patterns your language has quietly taken over, which ones shrink to a single function, and which nine you still have to write by hand.


The claim everyone repeats, and what it actually said

Norvig's 1996 argument was narrow and correct: patterns exist partly to work around what a language cannot express. Give a language first-class functions, and Strategy stops being a class hierarchy and becomes an argument. Give it multiple dispatch, and Visitor's whole accept()/visit() dance evaporates.

Two things get lost when the "16 of 23" line is quoted today:

  1. He was counting Lisp and Dylan, which in 1996 already had first-class functions, macros and multiple dispatch. Mainstream languages spent the next three decades slowly catching up — and they still have not caught all the way up.
  2. "Invisible or simpler" is two different verdicts. A pattern that becomes one line of syntax and a pattern that becomes a five-line function are not the same outcome, and conflating them is what produces the unhelpful conclusion that "patterns are dead."

So we used three verdicts instead of two, and applied them to every pattern in every language.


How we classified all 23 patterns

Design patterns still relevant — all 23 Gang of Four patterns sorted into absorbed, collapsed and intact across seven languages

The test for each pattern was simple: write it idiomatically in each language, then ask what is left of the book's structure.

Verdict Definition What it means for you
Absorbed The language ships the pattern as a feature Writing the GoF classes is usually wrong — you are reimplementing syntax
Collapsed The intent survives; the class hierarchy does not Use a function, closure, module or table. Reach for classes only in the stated exceptions
Intact You still write approximately what the book describes No language removed the underlying problem

One rule kept the classification honest: a pattern only counts as absorbed or collapsed if the idiomatic version in a majority of the seven languages no longer needs the structure. Patterns that shrink in Python but not in Java stayed under Intact with the exception noted.

Final count: 14 of 23 absorbed or collapsed, 9 intact. Norvig got 16 of 23 for Lisp in 1996. Mainstream languages in 2026 have absorbed slightly less than Lisp had already absorbed thirty years ago — which is a more interesting result than either "patterns are dead" or "patterns are eternal."


The 4 patterns your language already ships

These are absorbed. The problem still exists; you no longer write the solution.

Pattern What ships it Still write it by hand when
Iterator Generators and for-each in 5 of 7 languages — yield, for…of, IEnumerable, and Go's iter.Seq since 1.23 You need a cursor you can inspect, reset, copy or serialise — a generator's state is deliberately opaque
Strategy First-class functions. A strategy is a function; a "parameterised concrete strategy" is a closure The strategy has several related methods, or must be discovered/registered by name at runtime
Flyweight String interning, Java's Integer.valueOf cache, CPython small ints, Symbol.for, Go's time.LoadLocation You need a domain pool — shared meshes, event schemas, tile types — that the runtime knows nothing about
Proxy JavaScript's built-in Proxy, C#'s Lazy<T> and DispatchProxy, Java's reflect.Proxy behind Spring and Hibernate You are in Go or C++, which pointedly refuse to generate proxies — and consider that a feature, not a gap

The Iterator case is the cleanest example of absorption in the whole catalogue. GoF describes a hasNext()/next() object holding a cursor. In Python, JavaScript, C# and now Go, yield turns the function's own local variables into that cursor and deletes the class entirely. The pattern did not become wrong — it became syntax.

The tell that you are re-implementing syntax: if your class exists only to hold a position, a boolean and a next(), your language almost certainly has a keyword for it.


The 10 patterns that collapse into a function

These are the interesting ones. The intent is as useful as it was in 1994; the class hierarchy the book drew is not.

Pattern What replaces the classes Keep the full pattern when
Singleton A module (Python, JavaScript), Lazy<T> (C#), sync.Once (Go) Never, really — the interesting question is whether you need a singleton at all, not how to write one
Factory Method A function, or a function field on a struct Subclasses genuinely differ in more than which type they create
Abstract Factory A struct or NamedTuple bundling the family, built by one function per family The whole family must be swapped at runtime, not merely chosen at startup
Builder Keyword arguments (Python), init + with (C#), functional options (Go) Java or C++, which have no named optional arguments; or steps accumulate; or validation must run once at the end
Facade A module or package with a small exported surface — __all__, a barrel file, unexported types You need the five dependencies injected and faked in tests
Chain of Responsibility A list and a loop — the order becomes data you can print, test and configure Handlers must run work before and after the rest of the chain (this is what middleware does)
Command A closure, Runnable, std::function, a func() on a channel You need somewhere to put captured state: undo, an audit log, or a serialisable form for a queue
Mediator A rules object keyed by event; React calls this "lift state up" Never needs the full GoF interface — but the centralisation decision is as important as ever
State A discriminated union with an exhaustive switch, or a transition table Each state has rich behaviour of its own rather than being bookkeeping
Visitor Pattern matching: TypeScript unions, C# switch expressions, std::variant, Java 21 sealed interfaces The set of node types is open — code you do not control contributes types you cannot enumerate

Notice what several rows have in common. TypeScript, C#, C++ and Java 21 do not merely make Visitor and State shorter — they make them safer, because adding a case turns every incomplete switch into a compile error. The classic class-based versions cannot do that. This is the part of the story the "patterns are dead" posts miss: some patterns were not replaced by less structure, but by better-checked structure.


The 9 patterns you still write by hand

No language removed these, and looking at them together explains why.

Pattern Why it survived
Prototype Every language's cheap copy is shallow — Go's cp := *t, C#'s with, JS spread, copy.copy. All compile, all silently share nested state. The hard part is unsolved everywhere
Adapter The work is semantic translation — cents to dollars, "DECLINED" to an exception. No type system does that for you. In Go it is not even optional: you cannot add methods to another package's type
Bridge Two hierarchies multiplying is a design problem, not a syntax problem. database/sql and JDBC are bridges
Composite Part-whole trees are inherent to the data. (For a closed set of node types, a union is a real alternative)
Decorator Languages productised it instead of removing it — Go middleware, C#'s DelegatingHandler, Express
Interpreter Still the only safe way to run user-supplied rules. The alternative is eval(), which is remote code execution with a friendly name
Memento The deep-copy trap catches every language. Immutability collapses it — which is exactly why immutable state is worth the trouble
Observer Subscribing is three lines everywhere. Unsubscribing safely, iterating a list a callback may mutate, and stopping one listener's exception from eating the others are still yours to solve
Template Method Inverted rather than removed: in Go it becomes a function plus an interface — a shape worth stealing in every language

Why those nine survived: the pattern behind the patterns

Read the two lists side by side and the split is not random.

The 14 that faded were compensating for a missing language feature. Strategy, Command and Factory Method all exist because 1994 Java could not pass a function as an argument, so you wrapped the function in an object and called the object a pattern. Give the language closures and the wrapper disappears. Visitor exists because most languages dispatch on one type; give them pattern matching and the double bounce disappears.

The 9 that survived describe relationships between objects. Adapter is about a boundary you do not control. Bridge is about two things growing independently. Composite is about the shape of your data. Observer is about who finds out when something changes. No amount of syntax removes those questions, because they are questions about design, not about expressiveness.

That gives you a genuinely useful rule of thumb:

If a pattern's main job is to carry behaviour around, your language probably already does it. If its main job is to arrange relationships between objects, you still have to design it.

At SolutionGigs, this is the distinction we ended up building the whole handbook around — and it is the one we would have wanted on day one of learning this material, instead of 23 UML diagrams presented as equals.


What changes when you change language

The single biggest thing writing 161 samples taught us: a tutorial that shows you identical code in seven languages is teaching you Java with different keywords. The real differences are large.

  • Go has no inheritance, which turns out to clarify more than it costs. Template Method inverts into a function plus an interface, Bridge becomes an embedded interface (the cleanest version of that pattern in any of the seven), and Adapter becomes compulsory. Go also refuses to generate proxies, so you write the struct — and there is no hidden machinery to debug at 3am.
  • Python and JavaScript need the fewest patterns and get the least help when they are wrong. Duck typing removes the naming problem but not unit mismatches, argument order or error conventions — and neither language can check that a visitor handled every node type, which is why our samples throw rather than return a plausible default.
  • C++ is the outlier that makes patterns stricter: the non-virtual interface idiom seals a Template Method more tightly than Java's final, friend enforces Memento's encapsulation at compile time, and std::variant plus std::visit refuses to build if a Visitor misses a case.
  • TypeScript has the strongest version of several patterns purely through types — branded types stop the Flyweight key-collision bug, and a never guard turns "you forgot a case" from a production surprise into a build error.
  • Java and C# are where the classic implementations still read most naturally, which is exactly why most tutorials are written in them — and why they give a misleading impression of the other five.

What this means in a code review or an interview

The practical value of patterns moved. It is no longer "can you implement Visitor" — it is can you name the shape of a change so five people understand it in one word.

Three distinctions carry most of that value, and all three are routinely asked about:

  1. Strategy vs State: identical class diagrams. A client picks a strategy from outside and strategies never reference each other; a state replaces itself and states know their legal successors.
  2. Decorator vs Proxy vs Chain of Responsibility: all wrap and delegate. A Decorator always delegates and adds; a Proxy decides whether the call happens at all; a chain link may consume the request and stop it dead.
  3. Adapter vs Facade: an adapter is forced on you by a mismatch and usually wraps one object; a facade is a convenience you chose and usually wraps several. Delete an adapter and the code stops compiling; delete a facade and it still works, just worse.

If you are preparing for system-design rounds, the same "name the shape" instinct applies one level up — our guide to microservices vs monolith is the same kind of decision at architecture scale, and the PySpark interview questions guide covers the data-engineering equivalent.


Four mistakes this exercise made obvious

  • Applying a pattern because you learned it, not because the code asked for it. A base class with one subclass, a bridge with one implementor, a flyweight for ten thousand objects — all indirection, no payoff. The senior skill is knowing when to reject a pattern.
  • Porting Java patterns into Go or Python unchanged. A Singleton class in Python where the module already is one; an AbstractFactory interface in Go where a struct of interfaces says it better.
  • Ignoring what the pattern hides. Lazy Proxy loading gives you the N+1 query; a lazy Iterator can issue an HTTP request per loop iteration. Both look free at the call site — that invisibility is the cost of the abstraction.
  • Treating a class diagram as the pattern. Strategy and State share one. Adapter and Decorator nearly share one. The diagram is never the answer to "which is this?"

See each pattern run before you use it

Reading a UML diagram tells you the shape. It does not tell you why the shape matters — which is why the handbook we built puts a clickable simulator on every pattern page. Turn Flyweight off and watch memory climb from 24 MB to 2 TB. Remove the Adapter and watch three calls fail for three different reasons. Undo a forty-shape edit with a Memento and see why the inverse-operation approach would have needed forty coordinates.

All 23 patterns are live at solutiongigs.in/learn/design-patterns, each with the simulator, the honest pitfalls, and the code in all seven languages on one page — free, no signup, with your progress saved in your own browser. It is part of our wider free learning hub.


Frequently Asked Questions

Are design patterns still relevant?

Yes, but not evenly. Writing all 23 patterns in seven languages, we found 4 are shipped by the language itself, 10 collapse from a class hierarchy into a function or a module, and 9 still require essentially the structure the book describes. The vocabulary is more relevant than ever — being able to say "this is a Decorator" in a review and have everyone understand the change is the durable value. Roughly 60% of the original implementations are not.

Which design patterns are obsolete?

None are obsolete as ideas, but several are obsolete as code you write. Strategy is a function in Python, JavaScript and TypeScript. Iterator is a generator in five of the seven languages. Singleton is a module. Writing the GoF class hierarchy for these in a modern language usually adds ceremony without adding safety — and reviewers will read it as unfamiliarity with the language rather than knowledge of patterns.

Did Peter Norvig say 16 of 23 design patterns are unnecessary?

Close, but the wording matters. In his 1996 talk Design Patterns in Dynamic Languages, Norvig observed that 16 of the 23 become "invisible or simpler" — not unnecessary — in a dynamic language, and he was writing about Lisp and Dylan. Re-running the test across Java, Python, C++, C#, JavaScript, Go and TypeScript, we counted 14. Mainstream languages have absorbed slightly less than Lisp had already absorbed thirty years ago.

Do I still need design patterns in Go?

Yes, but different ones in a different shape. Go has no inheritance, so Template Method inverts into a function plus an interface, and Adapter becomes mandatory rather than optional — you cannot add a method to another package's type. The standard library leans hard on the survivors: database/sql is a Bridge, http.Handler middleware is Decorator and Chain of Responsibility, driver.Driver registration is a Factory.

Should I still learn design patterns for interviews?

Yes. Interviewers rarely ask you to implement Visitor from memory; they describe a messy situation and check whether you can name the shape of the fix. The high-value knowledge is comparative — Strategy versus State, Decorator versus Proxy, Adapter versus Facade — because those distinctions are exactly what a candidate who has only memorised diagrams cannot explain.

What is the difference between a design pattern and a language feature?

A pattern is a named solution to a recurring design problem; a feature is a built-in mechanism. When a language absorbs a pattern the problem does not disappear — you just stop solving it by hand. A Python for loop is the Iterator pattern; you simply do not implement hasNext(). This is why patterns survive as vocabulary long after the code that expressed them is gone.

Which design patterns should I learn first?

Start with the five you will meet in the first month of any codebase: Strategy, Factory Method, Observer, Decorator and Adapter. Add Singleton early, mostly so you understand why experienced engineers are wary of it. Interpreter and Visitor can wait — they are real, but rare outside compilers, linters and rule engines. For the full sequence and a schedule, see our guide on how to learn design patterns.


Conclusion

The honest answer to "are design patterns still relevant" is a number, not an opinion: 9 of 23 still need writing, 10 collapse to a function, and 4 are already in your language. That is a far more useful conclusion than either camp's slogan, and it changes what you should spend time on.

Learn the vocabulary for all 23, because naming a change is what makes code review fast. Learn the implementations for the nine that survive, because those are the ones you will actually type. And when you catch yourself writing a class whose only job is to hold a function, check whether your language solved that in 2011.

Every one of the 23 is live at solutiongigs.in/learn/design-patterns — with a simulator you can click, the trade-offs stated honestly, and the code in Java, Python, C++, C#, JavaScript, Go and TypeScript side by side. It is free and there is nothing to sign up for. Start with Strategy if you want to see a pattern almost disappear, or Adapter if you want to see one that never will.

Mohammed Yaseen

Mohammed Yaseen

Founder, SolutionGigs

Mohammed built the SolutionGigs Design Patterns handbook — all 23 Gang-of-Four patterns with interactive simulators and 161 code samples across seven languages — and wrote every one of those samples by hand to find out which patterns modern languages had quietly made redundant. LinkedIn →

Design Patterns: The Interactive Handbook

Free, no signup — right in your browser.

Design Patterns: The Interactive Handbook →
Found this useful? Share it.
ShareXLinkedIn

Comments

0

Join the conversation. Sign in to leave a comment — we'd love to hear your thoughts.