design-patterns ·javascript ·software-development ·ai ·code-quality

Your Language Solved This. Your AI Did Not Get the Memo.

5/14/2026

6 minutes read

You ask an AI to implement a discount system. The task is simple: apply one of several discount rules to an order total. It comes back with a DiscountStrategy interface, a DiscountContext class that holds a reference to it, three concrete strategy classes (PercentageDiscount, FlatDiscount, FreeShipping), and a factory function to instantiate the correct one from a config string.

In JavaScript.

The discount logic is seven lines. The pattern scaffolding around it is eighty. If you ask the AI why it structured it this way, it will tell you, correctly, that this is the Strategy Pattern, and the Strategy Pattern is how you handle interchangeable algorithms.

It is not wrong about the pattern. It is wrong about the year.

What AI Generated
What JavaScript Needed

The Book That Started It

The Gang of Four published Design Patterns: Elements of Reusable Object-Oriented Software in 1994. The four authors, Gamma, Helm, Johnson, Vlissides, were primarily working in Smalltalk and C++. The twenty-three patterns they documented were not universal laws of software design. They were solutions to specific problems those languages had.

C++ did not have first-class functions. You could not pass behavior around directly. If you wanted interchangeable algorithms, you had to represent behavior as an object: a class with a method, an interface to abstract it, concrete implementations for each variant. The Strategy Pattern is the formal name for this workaround.

Peter Norvig recognized this in 1996, two years after the book's release. In a talk titled "Design Patterns in Dynamic Languages," he walked through all twenty-three patterns and found that sixteen of them become invisible or significantly simpler in a language with dynamic typing and first-class functions. Not unnecessary. Invisible. The pattern collapses into a language feature so completely that it stops being a pattern and starts being syntax.

In JavaScript, applyDiscount(order, percentageDiscount) is Strategy. You just call it passing a function.

Each pattern is a bug report.

Think about what a bug report is: a precise description of something a system cannot do. Strategy is a bug report that reads "this language cannot pass behavior as a value." Builder reads "this language cannot express optional construction arguments." Iterator reads "this language has no native way to walk a sequence." Each pattern names a gap: not in your problem domain, but in the language you are working in. Once you read them that way, the twenty-three patterns stop being a catalog of solutions and become a taxonomy of constraints. A historical record of what the tools of 1994 could not express, and what developers built to work around that.

What the Language Already Did

The clearest cases:

Strategy and Command exist because Java and C++ cannot pass behavior directly. In JavaScript, you pass a function. There is no interface to define, no context class to hold a reference, no concrete implementations to instantiate. The language feature is the pattern.

Singleton exists because C++ and Java need explicit mechanisms to ensure a class has only one instance. In JavaScript, a module is evaluated once and cached. export const db = new Database() is a Singleton. You are writing one every time you export a shared object.

Iterator was a significant structural pattern in C++. Java required explicit Iterator implementations with hasNext() and next() methods. Modern JavaScript has for...of and generators. The pattern is now punctuation.

Builder solves a specific Java problem: constructors cannot have default or named arguments, so constructing a complex object requires a separate builder class with chained method calls. In JavaScript and Python, you pass an options object with defaults. createServer({ port: 3000, timeout: 5000 }) is Builder. It takes one line.

Factory Method in Java requires a class hierarchy where subclasses decide which class to instantiate. In JavaScript, functions are objects, classes are first-class values, and you can pass a constructor as an argument. The factory is the function.

Facade is a module with a clean public API. Export what you want callers to use, keep the rest internal. This is what index.js is for.

This is not a comprehensive tour of all twenty-three. It is enough to establish the pattern: a significant portion of GoF was solving problems your language's runtime already handles. Norvig counted sixteen. The number has only grown since 1996.

The Patterns That Survived

This is not an argument that patterns are useless. Some of them are load-bearing regardless of language.

Structural patterns (Adapter, Proxy, Composite, Decorator) describe relationships between components at an architectural level, not a language level. If you are wrapping a third-party API that speaks a different interface, you write an Adapter in Python the same way you write one in Java. The language did not solve this problem because the problem is not a language problem.

The vocabulary also survived. Saying "this is a Strategy" in a code review communicates intent even when the implementation is a single argument. The concept names a recurring problem. That is valuable independent of how many lines it takes to solve. Patterns function as cognitive compression: a named handle for a complex interaction reduces the working memory cost of reasoning about a system. That value is real even when the implementation has collapsed to a lambda.

The argument is not "stop learning patterns." It is "understand why each pattern exists before reaching for it." If you know that Strategy exists to simulate first-class functions in a language that lacks them, you can ask the relevant question whenever you encounter a new situation: does my language already have this? If yes, the implementation is a line. If no, you build the structure. Either way, you made the decision deliberately rather than by habit.

The Problem Is Back

Here is why this is worth discussing in 2026 and not 2006: AI code generation is actively reversing the progress.

When a developer asks an AI to implement a discount system without specifying architecture, the AI reaches for the canonical pattern. Andrej Karpathy documented this directly last year: LLMs "overcomplicate abstractions" and "implement a bloated construction over 1000 lines when 100 would do." A Harvard research paper from 2025 named the phenomenon "architectural mimicry": LLMs generating code with the appearance of good architecture, correctly separated, named patterns and all, while producing hidden coupling that violates the principles those patterns are meant to enforce. A separate arxiv study tested eleven major language models and found that Singleton and Factory are systematically over-predicted; models apply them in contexts where they do not fit because they appeared frequently in training data and the model has no experience of what maintaining that structure actually costs.

The mechanism is not mysterious. Training data skews toward codebases written before 2015, tutorials explaining canonical patterns in Java, Stack Overflow answers showing the textbook implementation. The model pattern-matches "this looks like a problem requiring interchangeable algorithms" and reaches for Strategy because Strategy is how this problem was solved in the data it learned from. It does not know that the target language made the problem disappear.

This is not an argument against AI tooling. The problem is specific: it surfaces in prompt-driven workflows where the developer hands the AI both the design and implementation responsibility with no upfront constraints. In architecture-first development, where structure and contracts are defined before code is written, the AI fills in implementation within boundaries that prevent it from reaching for the wrong pattern. The problem is not the tool. It is the workflow.

What This Actually Requires

The most useful skill a developer can bring to AI-assisted development is the ability to recognize when AI gave them a solution to a problem they do not have.

That skill comes from knowing why patterns existed. If you understand that Strategy is a first-class-function workaround, you can look at eighty lines of TypeScript pattern scaffolding for a seven-line problem and say: "The language already solved this. Pass the function." If you do not understand why Strategy exists, you see a well-structured, professionally-named implementation and accept it.

The GoF book is worth reading. Not as a catalog of solutions to apply, but as a record of constraints: a documented account of what C++ and early Java could not express and what developers built to work around those limitations. Reading it that way, each pattern becomes diagnostic. Strategy tells you about languages without first-class functions. Builder tells you about languages without named parameters. Iterator tells you about languages without native enumeration.

You are reading bug reports. Thirty-year-old bug reports for languages most of your code does not run in.

The patterns that dissolved did so because your language closed the bugs. The patterns that survived did so because the underlying problem has nothing to do with language features. Knowing the difference is what makes you useful in the room where the code is being written.