How to Read a Pattern Card
A note on how to read pattern cards
Β· Memo guide Β· 24 min read

All models are approximations. Essentially, all models are wrong, but some are useful. However, the approximate nature of the model must always be borne in mind.
β George E. P. Box
What is a good program? It is one that handles many situations without breaking down. How difficult and absurd a definition that is. What constitutes a "situation," what counts as "handling" it, and what "many" means are all things that can only be accumulated through experience. A design pattern catalog cannot escape this question, because the catalog itself is nothing more than a collection of recurring situations given names.
A typical pattern card is a description of a situation: a static artifact that assumes a certain scenario in advance and records the recurring problems and structures within it. Real programming, by contrast, keeps moving. Library contracts change, hardware budgets fall short, external services slow down, and deployment and rollback conditions reshape the design. Trends shift, and user experience evolves from one generation to the next. When the definition of good UX changes, the software built to sell must change along with it.
A catalog, therefore, cannot contain the infinite breadth of reality. So what does programming, and what does a catalog, actually do? It distills recurring situations and extracts the parts that can be stated in common. This post explains what my notes are and how to apply a static card to a dynamic project.
A catalog is lossy compression
A pattern, which is to say a commonalization, is compression. Compression always means deciding what to keep and what to discard.
What a catalog keeps is structure: participants, responsibilities, call relationships, things to be separated, and named options. These can be drawn as diagrams and carried over to other projects. Because they have names, you can invoke them in a meeting with a single word instead of a long explanation. The ultimate use of a pattern catalog is as a communication tool that compresses a large amount of information at once, and much of its value comes from exactly that.
But a catalog throws away the local conditions that made that structure a good answer.
Every item in the right-hand column is local: it applies only to this team, this repository, this branch, this operating environment. Putting it into a catalog would mislead the next reader. The catalog discards it not out of laziness, but as a result of compression that keeps only what is transferable.
The trouble is that whoever receives the card reads only the left side and forgets a right side ever existed. Once only the structure is left, the solution is the first thing you see, and the conditions that made it the answer are gone. That is why a programmer has to keep thinking.
Does the scenario this pattern assumes actually exist in my situation? Does it fit my project? Does it truly align with the user experience (UX) I have designed? And you must adapt it as you integrate it into your own code.
So are the patterns written in a catalog just useless dead knowledge that crumbles in front of real-world constraints?
Absolutely not. As George Box famously said, "All models are wrong, but some are useful." Patterns are useful not because they are perfect answers, but because they provide a baseline for adaptation.
Inventing your own architecture from scratch on the open sea carries enormous cognitive cost and risk. Patterns define the starting point from which we begin. The moment you declare "we are using the Strategy pattern here," your teammates' minds instantly decompress a shared image of an encapsulated, swappable algorithm structure. Dozens of diagrams and long meetings are replaced by a single word, an extreme gain in communication efficiency. The "lossy compression" a catalog provides becomes a language in its own right.
A programmer's job is not to worship patterns but to push the catalog, that "lossy-compressed standard", into the middle of the project, then pull back in the fragments of reality that compression discarded, the team's skill level, the server's latency, the project manager's feature requirements, and willingly warp the pattern into a shape of our own.
We do not learn patterns in order to apply them verbatim. We learn patterns so we can break and twist them safely and elegantly. In the end, a good programmer is not someone who blindly trusts the static map called a pattern, but someone who holds that map, steps into the dynamic territory, and is willing to tear it apart and revise it to match the actual terrain. I write pattern cards partly to document the situations I have encountered, but also to revisit and reflect on my past mistakes in implementation.
What was discarded already has a name
Christopher Alexander, an architect, described patterns in terms of the relationship between a context, the recurring problem within that context, and the solution. The term "pattern" in programming was borrowed from this renowned architect's methodology. A solution does not stand alone.1
The contexts Alexander described hold forces that collide or pull against each other: conditions hard to satisfy at once, like cost against performance, independence against simplicity, local adaptation against standardization. A pattern is the name attached to an arrangement that strikes an acceptable balance among those forces.
To Alexander, a "Problem" was not simply a state that throws an error but a state in which conflicting Forces pull taut against one another, and a Solution does not fall from the sky: it is a physical "Resolution" where those forces find a compromise and settle.
The arrangement of Forces, of requirements, comes first, and the solution arrives as a response to that arrangement. Yet whoever receives the card usually sees the solution first and asks only afterwards whether this is the situation for it. Sometimes they never ask.
Why do people not ask about the forces and constraints they face? Cataloging every aspect of their situation and thinking through it systematically is a painful exercise: on a project layered with abstractions, you must figure out how far down to peel them back. Sometimes it is enough to reason at the level of abstraction; other times you need to be able to shed that cognitive load. The first principle of programming differs from person to person, but for me it is: what am I trading? It is about trade-offs. Knowing which trade-off to make at which moment is built through experience, and it is what a programmer must genuinely think about. Sometimes you give up memory to gain performance; other times you sacrifice performance for maintainability.
Real programs are dynamic because of constraints
Onto the situation a pattern card abstracts, a real project stacks constraints that vary with each programmer's circumstances.
Even the same requirement needs a different design once the constraints differ.
Constraints are the chisel that carves away the pieces of code that will become the program.
Situation | First solution that comes to mind | Actual constraints | Resulting design change |
|---|---|---|---|
Large file conversion | Load everything into memory and process | Memory ceiling and concurrent requests | Streaming, chunking, backpressure |
External payment call | Retry immediately on failure | Risk of duplicate payment | Idempotency key, limited retries, compensation procedure |
Large list query | Construct all domain objects | N+1 queries and latency | Fetch only needed columns, pagination, read model |
Screen update | Change state only after server response | High latency and user interaction | Temporary UI state, reconciliation with server result |
- Situation
Large file conversion
- First solution that comes to mind
Load everything into memory and process
- Actual constraints
Memory ceiling and concurrent requests
- Resulting design change
Streaming, chunking, backpressure
- Situation
External payment call
- First solution that comes to mind
Retry immediately on failure
- Actual constraints
Risk of duplicate payment
- Resulting design change
Idempotency key, limited retries, compensation procedure
- Situation
Large list query
- First solution that comes to mind
Construct all domain objects
- Actual constraints
N+1 queries and latency
- Resulting design change
Fetch only needed columns, pagination, read model
- Situation
Screen update
- First solution that comes to mind
Change state only after server response
- Actual constraints
High latency and user interaction
- Resulting design change
Temporary UI state, reconciliation with server result
There is no reason to always use streaming or always apply a retry policy. There is no need to layer in a complex Policy for problems that a script-level solution can handle just fine. You must always be aware of which constraint is the one that changes the design. Code that ignores its constraints will be slow in production, execute things twice, or lose its recovery path after a failure.
A program's container must own its axis of change
Absorbing change does not mean hiding everything behind a large abstraction. A good boundary owns one kind of axis of change along with the rules that go with it.
Software engineering can be viewed, among other mental models, through the lens of "Encapsulation of Volatility": keeping in one place what can change, who decides that change, and how far to roll back on failure. Without a boundary, policy appears as scattered if statements, exception handling, retries, and environment branching at every call site. Conversely, when a boundary is too large, unrelated changes become coupled inside a single module.
So using a pattern means less introducing a structure than deciding which boundary the axis of change and the responsibility go on, and that call is the programmer's.
How to use a static card dynamically
The fact that the pattern cards I write are static cannot be changed, because no matter how faithfully you draw a map to mimic reality, you cannot capture the entirety of terrain that changes in real time. For that reason, you must read a pattern card not as a solution but as a claim. Every pattern rests on a claim about what changes and what does not.
A programmer named Dongwoo Jeong thought and implemented things in a certain way in the past; you must think differently and push back. And my future self must also contend with the claims my past self made, because the right answer then is not necessarily the right answer now.
Turn it into a claim and you can shake it. Take the event-delivery card, and these questions come up.
Is it acceptable for the notification order to change?
If an exception is thrown mid-notification, what happens to the remaining subscribers?
What happens if a subscriber unsubscribes while a notification is in flight?
If notifications fire from several threads at once, which contract holds?
When are subscribers that were never released reclaimed?
May a notification be lost, or must it always arrive?
The card will wobble under those questions, or you never thought of them while you were using the card. The point is only that the arrangement of forces the card assumed is not the arrangement you have. Where there is a single thread, few receivers, and a lost notification that is not fatal, the card can still be a good answer, and still not be the answer later.
When you get a card, the job is not to apply the solution straight away but to reconstruct the arrangement it assumed and compare it with the arrangement you have now.
Even if the catalog has discarded its context, the programmer must preserve the context within the project. For example, if the reason for placing a boundary around storage access is recorded, whether it was because of a planned storage migration, for testability, or simply out of convention, that decision can be re-evaluated later. Without that record, only the structure remains, and no one can touch it.
Multiple patterns create the same boundary
A single card addresses a local situation. When those cards accumulate, they form a boundary. Architecture and layers emerge when multiple cards share the same forces. If a card is the claim "this change is absorbed here," then architecture is what decides which such claims can be grouped together and which must be separated.
Putting patterns in one folder does not make an architecture. One boundary forms when the cards handle the same external dependency's failures and changes together and a single owner makes the final call on that policy; callers then need only the contract of that boundary, not the supplier's detailed errors or retry counts.
Layers arise the same way: screen state and user input belong near the view, use-case sequencing and authorization checks belong on the application side, domain rules are separated from the storage medium, and the details of the network and database can be pushed outward. This separation is correct not because the names are familiar, but because the rate of change, the failure mode, the owner, and the testing approach differ for each part.
So what is a programming architecture? It is not simply a large count of applicable patterns. It is the broad result of making many local solutions answer a higher-level situation together. Even when each pattern fits on its own, mixing policies from different boundaries in one place can make the architecture worse.
When judging whether to raise a boundary, think through the following.
Do these cards actually change together for the same reason?
Do the failures and resource budgets these cards handle belong to the same owner?
Can this policy be hidden behind a single contract rather than repeated at every call site?
After the boundary is created, can the internal policy still be observed and re-evaluated?
If most answers are no, it is still just several cards. Force a layer name onto them and only the structure grows while the axes of change stay scattered. What counts is how consistent a division of problems you produced inside your own logic.
If that division of problems is consistent inside your own logic, or your organization's, and stays in step with the physical constraints of reality, then that is the architecture.
That is why a pattern is nothing more than a conclusion
In his 1996 OOPSLA keynote, Christopher Alexander observed that software patterns are useful as a form for exchanging ideas, but they do not sufficiently address the capacity to generate a coherent whole or the effect that whole has on human life.2 That observation is not a call to abandon patterns. It is a recognition that collecting individual patterns and producing one coherent result from those patterns are two different things.
A pattern is a conclusion already settled. But a pile of conclusions does not make a good program: unless the questions and constraints that produced the conclusion are restored, along with the conflicting forces and the process of choosing, the pattern is left as a structure that has lost its context. Nor do conclusions that came from different questions form one whole just because they sit together.
Saying a good program affects human life is not abstract art theory. It means something thoroughly practical: the user solves their own problem more smoothly, business value comes out of that, and the developer who runs the system gets through the small hours a little safer.
For a set of patterns to become a "coherent whole", the physical constraints the system sits inside β cost, latency, legacy, the odds of failure β and the user experience have to come first. The purpose of a program is not to put perfect patterns on display.
A pattern is a map that compresses reality. The programmer's job is not to worship that map but to restore the constraints and questions it leaves out and fix it for the territory you are standing in.
Footnotes
- Christopher Alexander, Sara Ishikawa, Murray Silverstein et al., [A Pattern Language: Towns, Buildings, Construction](https://arl.human.cornell.edu/linked%20docs/Alexander_A_Pattern_Language.pdf), Oxford University Press, 1977. β©
- Christopher Alexander, "The Origins of Pattern Theory, the Future of the Theory, And the Generation of a Living World," OOPSLA keynote, 1996. Transcript β©