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.
What the catalog discards, however, are 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 problem lies in the fact that the person receiving the card sees only the left side and forgets that a right side ever existed. When only the structure remains, the solution becomes the first thing visible, while the conditions that made it a good solution disappear. That is precisely why programmers must always think critically.
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.
Does that mean the patterns in a catalog are useless dead knowledge that crumbles before 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 take the "lossy-compressed standard" of a catalog and push it into the center of the project, then willingly bend it into a shape of your own by pulling back in the fragments of reality that were discarded during compression: your team's level of expertise, your server's latency, your project manager's feature requirements.
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 contain forces that conflict with or pull against one another: conditions that are difficult to satisfy simultaneously, such as cost versus performance, independence versus simplicity, or local adaptation versus standardization. A pattern is the name given to an arrangement that achieves an acceptable balance among those forces.
For Alexander, a "Problem" was not simply a state in which an error occurs, but a state in which conflicting Forces are pulling taut against one another. A Solution, he said, does not fall from the sky; it is a physical "Resolution" where those conflicting forces find a compromise and come to rest.
The arrangement of Forces, that is, of requirements, comes first, and the solution emerges as a response to that arrangement. Yet the person receiving the card typically sees the solution first and only later asks whether the situation calls for it. Sometimes they do not ask at all.
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
On top of the abstracted situation that a pattern card describes, the actual constraints of a real project are layered in, varying by the programmer's circumstances.
The same requirement calls for a different design when the constraints differ.
Constraints are the chisel that carves away at the 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.
Using a pattern, therefore, means less "introducing a structure" and more "deciding which boundary will own the axis of change and the responsibility," and that is the programmer's role.
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.
Reframing as a claim makes it something you can challenge. Taking an event-delivery card as an example, the following questions arise:
Is it acceptable for the notification order to change?
If an exception occurs during notification, what happens to the remaining subscribers?
If a subscriber unsubscribes in the middle of a notification, what happens?
If notifications occur simultaneously from multiple threads, what contract is upheld?
When are subscribers that have not been released reclaimed?
Is it acceptable for a notification to be lost, or must it be guaranteed to arrive?
The card will be shaken by these questions, or perhaps these questions were never considered when the card was written. The key point is simply that the arrangement of forces the card assumed differs from the current arrangement. In a single-threaded environment with a small number of receivers where losing a notification is not critical, the card may still be a good answer, though it may not be the right answer in the future.
When you receive a card, the task is not to apply the solution immediately, but to reconstruct the arrangement the card assumed and compare it with the current arrangement.
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.
Placing patterns in the same folder does not make an architecture. A single boundary forms when the cards collectively address the same external dependency's failures and changes, and there is one final decision-maker for that policy. Callers need not know the supplier's detailed errors or retry counts; they only need to know the contract of that boundary.
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 knowing a large number of applicable patterns. It is the comprehensive result of making many local solutions answer a higher-level situation together. Even if each pattern fits on its own, mixing policies from different boundaries in one place can degrade the architecture.
When deciding whether to elevate a boundary, consider the following:
Do these cards actually change together for the same reason?
Do these cards share the same owner of failures and resource budgets they handle?
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, you still have nothing more than a collection of individual cards. Forcing a layer name onto them only grows the structure while the axes of change remain scattered. What matters is how consistently you have distinguished problems within your own logic.
If, within your own logic or your organization's logic, synchronized with the physical constraints of reality, that distinction of problems is consistent, 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 already a settled conclusion. But collecting conclusions alone does not make a good program. If the questions and constraints that gave rise to those conclusions, the conflicting forces and the process of choosing among them, are not reconstructed, the pattern remains a structure stripped of its context. Nor do conclusions drawn from different questions form a coherent whole simply because they are gathered in one place.
Saying that a good program affects human life is not abstract aestheticism. It is a thoroughly practical statement: it means helping users solve their problems more smoothly, creating business value as a result, and keeping the developer who runs the system safe through the small hours of the morning.
For a collection of patterns to become a "coherent whole," the physical constraints the system faces, cost, latency, legacy code, likelihood of failure, and the user experience must come first. The purpose of a program is not to put perfect patterns on display.
A pattern is a map that compresses reality. A programmer's job is not to worship that map, but to restore the constraints and questions it left out and revise it to fit the current territory.
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 โฉ