Skip to content
MEMO GUIDE

How to Read a Pattern Card

A note on how to read pattern cards

Jeong Dongwoo ยท 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.

Loading animated diagram...

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

Loading animated diagram...

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.

Loading animated diagram...

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.

Loading animated diagram...

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.

Loading animated diagram...

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.

Loading animated diagram...

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.

Loading animated diagram...

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.

Loading animated diagram...

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:

  1. Do these cards actually change together for the same reason?

  2. Do these cards share the same owner of failures and resource budgets they handle?

  3. Can this policy be hidden behind a single contract rather than repeated at every call site?

  4. 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

  1. 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. โ†ฉ
  2. Christopher Alexander, "The Origins of Pattern Theory, the Future of the Theory, And the Generation of a Living World," OOPSLA keynote, 1996. Transcript โ†ฉ