Twelve anti-patterns, in four families
coreintermediateTwelve recurring mistakes, and they group into four families by what each one skips. Skipping the thinking: starting from technologies instead of requirements, having no capacity estimates, and designing for global scale nobody asked for. Choosing the wrong default shape: microservices by default, one shared database with no owner, and synchronous calls for work that takes minutes. Having no failure model: unbounded retries, no idempotency, and no written answer to "what happens when this dependency is down". Being blind in production: ignoring tail latency, treating a cache as the source of truth, and having no observability. Grouping them matters because twelve loose items get memorised and four families get recognised, and recognition during a live review is the only place the list is worth anything. They also compound: no capacity estimate leads to no tail-latency target, which leads to no timeout, which turns a slow dependency into unbounded retries, which turns one slow service into a system-wide outage. That chain is a single root cause wearing four names. The fix for each is a mechanism rather than a slogan — a number, a bound, a deadline, an owner — and the honest form of every one of these is not "we did it wrong" but "we never decided".
Think of it as
Four questions a design can fail to ask, not twelve rules it can break. What is actually required? What shape does that require? What happens when part of it fails? How would we know? Each unasked question produces its own family of symptoms, which is why fixing one member of a family without asking the question usually surfaces the next member a month later.
What we're doing: Read one real design document and name which family each gap belongs to.
- 4
- The tell for this family is ordering: a design that names products before requirements has usually chosen the products first and will bend the requirements to fit them.
- 20
- Unbounded retries are the most reliably damaging item on the list, because they convert a partial failure into a load multiplier aimed at the exact component that is already struggling.
- 30
- The cache-as-record failure is not "the cache might be stale" — it is that there is no store to rebuild from, so an eviction is data loss rather than a slow request.
- 41
- An average over a 1000x-variable workload is not a weak metric, it is an actively misleading one: it can improve while the slowest jobs get worse.
Why this works: The value of the four families is that they turn a review from twelve recall attempts into four questions asked of every page. Each finding here also names a fix with a mechanism in it — a job id, an attempt cap, a store behind the cache, a percentile target — so the review produces work rather than disapproval.
Reporting anti-patterns without naming the mechanism that fixes them
Wrong
Better
What you see: A design review that generates agreement and no diff: the author accepts every point, changes nothing structural, and the same findings reappear in the next review of the same system.
Why: An anti-pattern name describes a shape, not a change. Without a named mechanism there is no definition of done, so the fix defaults to whatever the author already intended — which is the design that produced the finding.
- Skipped the thinking
- Technologies first — requirements arrive on slide six
- No capacity estimates
- Global scale, no requirement
- Wrong default shape
- Microservices by default
- One database, no owner — a rename breaks four teams
- Sync calls for long work
- No failure model
- Unbounded retries — turns slow into down
- No idempotency
- No answer for a dead dependency
- Blind in production
- Ignoring tail latency — the mean hides the p99
- Cache as source of truth
- No observability
The twelve, grouped by what the design skipped
How four of them chain into one outage
Remember: Twelve anti-patterns, four families, one question each. What is actually required? (technologies first, no capacity estimates, global scale with no reason.) What shape does that require? (microservices by default, one database with no owner, synchronous calls for long work.) What happens when it fails? (unbounded retries, no idempotency, no failure model.) How would we know? (ignoring tail latency, cache as source of truth, no observability.) They compound into single outages, every one is really "we never decided", and the fix always names a mechanism with a number in it.
See also: start with requirements not technologies · estimation categories · averages hide tails · retry amplification · causes of cascading failures · why caching works · when not to split · dont go multi region without a reason

