21. Patterns Playbook — What Recurs, and What to Reach For Synthesis Across All 20 Problems

A. Why This Section Exists

Ten problems that look unrelated on the surface — a URL shortener and a collaborative document editor share almost no domain vocabulary — turn out, underneath, to be built from a surprisingly small, repeating set of moves. Once you notice that Ticketmaster's overselling fix, Rate Limiter's atomic increment, and Uber's driver-assignment race are the same underlying problem wearing three different costumes, you stop needing to re-derive the answer from scratch every time it shows up — you just need to recognize which named pattern applies, and why.

This section is that catalog: each pattern names the recurring shape of the problem, the concrete mechanism reached for, where it actually appeared (with links back to the specific deep dive), and — just as important — when that mechanism is the wrong tool. Section 0 (Prerequisites) explained what each building block is; this section explains which building block to reach for, given a symptom, which is the actual skill being tested in a live interview.

B. Pattern 1 — The Check-Then-Act Race

The shape of the problem: a request reads some shared state ("is this available?"), decides what to do based on what it read, then writes a change based on that decision — and the gap between the read and the write is exactly wide enough for a second, concurrent request to read the same stale state and make the same decision, resulting in two writes that should never have both succeeded.

The fix, in every case: collapse the read-decide-write sequence into a single atomic operation, so there is no gap in which a second request can observe the same pre-decision state.

Where It AppearedWhat Was RacingMechanism Used
Rate Limiter (P2, Deep Dive N)Two requests both reading "under the limit" before either increments the counterA single atomic Redis operation (or a Lua script bundling check + increment)
Ride-Sharing / Uber (P5, Deep Dive P)Two riders' requests both matching to the same available driverAn atomic conditional update (compare-and-swap) on the driver's assignment status
Ticketmaster (P8, Deep Dive M)Two buyers both reserving the same seatA single atomic conditional database update (UPDATE ... WHERE status='available'), checked via affected-row count
Notification System (P12)Two requests both checking "not yet seen" for the same dedup key before either claims itRedis's atomic SET key NX EX dedup_window — the check and the claim collapsed into one call
Distributed File Storage (P14)Two devices both committing a new version of the same fileA version-vector compare-and-swap at commit time — the one instance in this document where the "losing" request isn't rejected, but resolved into a conflicted copy instead
Payment System (P15)Two concurrent requests carrying the same idempotency key, both arriving before either has finished processingA unique database constraint on (merchant_id, key), enforced as a single atomic insert-if-absent on the idempotency record itself

When NOT to reach for an external distributed lock as the fix: if the entire check-then-act sequence can be expressed as one atomic database statement or one Redis command/Lua script, that's simpler and more robust than adding a separate lock service — reserve an external lock for when the protected operation genuinely needs to span systems a single atomic statement can't reach (Prerequisites E elaborates on this trade-off directly).

C. Pattern 2 — Keep Expensive Work Off the Hot Path

The shape of the problem: a user-facing request has one fast, essential part and one slow, non-essential part bundled together — and doing both synchronously means the user waits for the slow part even though it doesn't actually need to finish before a response can be returned.

The fix: do the essential part synchronously, hand the slow part off to an async queue and a background worker, and return to the user immediately. The user-visible latency becomes the fast part's latency only.

Where It AppearedThe Fast, Essential PartThe Slow Part, Moved Off-Path
URL Shortener (P1)Redirecting the userRecording click analytics
News Feed (P3)Accepting a new postFanning it out to every follower's feed
Video Streaming (P7)Accepting an uploaded fileTranscoding it into multiple resolutions
Video Streaming (P7)Serving a video playback requestAggregating/incrementing the view count
Ticketmaster (P8)Holding the seatN/A — deliberately the one place this pattern is not applied; see the callout below
Notification System (P12)Accepting a notification requestActually delivering it through the push/SMS/email provider
Ad Click Aggregation (P16)Accepting a click event into the ingestion logWindowed aggregation, HyperLogLog rollups, and fraud filtering
Yelp (P17)Serving a business's ratingRecomputing the aggregate rating — incrementally (an O(1) running-sum adjustment per new review) or on a short batch interval, rather than an O(n) rescan on every read

The important exception, worth stating explicitly: Ticketmaster's payment charge is not moved off the hot path the way analytics or view-counting are — it's sequenced strictly after the seat hold succeeds, not deferred to an async worker, because the user is actively waiting for a definitive yes/no on their purchase. Payment System (P15) generalizes this same exception across its whole design: nothing on the ledger-write path is deferred either, for the identical reason. This pattern applies specifically to work whose outcome the user doesn't need to see immediately — not to every "slow" operation indiscriminately.

D. Pattern 3 — Metadata / Blob Store Split

The shape of the problem: an entity has a small amount of structured, frequently-queried metadata (owner, timestamp, status) alongside a large binary payload (an image, a video file, a page of HTML) — and storing both together forces every system that touches the metadata to also carry the weight of the payload.

The fix: store the metadata in a database (fast to query, cheap to replicate) and the actual bytes in dedicated blob/object storage (I in Prerequisites), with the metadata row holding a pointer/key into the blob store.

Where It AppearedThe MetadataThe Blob
News Feed (P3)Post record (author, timestamp, caption)Attached photo/video
Chat System (P4)Message record (sender, timestamp, conversation_id)Attached file/image
Web Crawler (P6)URL, crawl timestamp, content hashThe fetched page's raw content
Video Streaming (P7)Video record (title, owner, status)Raw upload and each transcoded resolution
Google Docs (P9)Current revision numberPeriodic full-document snapshot
Distributed File Storage (P14)File record + ordered chunk-hash list + version vectorThe actual chunk bytes, deduplicated across the whole chunk store — even across different users' files

E. Pattern 4 — Idempotent Retries via Client-Generated Keys

The shape of the problem: a client sends a mutating request, the request actually succeeds server-side, but the client never receives confirmation (a timeout, a dropped connection) — and from the client's perspective this is indistinguishable from the request never having arrived, so a naive retry risks repeating a side effect that already happened.

The fix: the client attaches a stable, unique key to the request; the server remembers which keys it has already processed and, on a repeat, returns the original result instead of repeating the side effect.

Where It AppearedThe KeyThe Side Effect Being Protected
Chat System (P4)client_msg_idSending the same message twice
Ticketmaster (P8)Payment confirmation idempotency keyDouble-charging a customer's card
Job Scheduler (P10)Per-execution idempotency keyA job's side effect (e.g. sending an email) firing twice after an at-least-once delivery redelivers it
Notification System (P12)Client-supplied dedup key at ingest + a second check at the worker layerSending the same push/SMS/email twice on a client retry
Payment System (P15)Mandatory idempotency key at the API boundaryDouble-charging a card on a client or network retry — the highest-stakes instance of this pattern in the document
Ad Click Aggregation (P16)Idempotency key + Bloom-filter pre-filterCounting the same click twice from a retried/duplicated event
Distributed Message Queue (P20)Consumer-side idempotent processing, paired with broker-level at-least-once deliveryReprocessing a message the broker redelivers after a consumer crash/rebalance — the capstone example: the pattern applied by the queue's own consumers, not just by systems that happen to use a queue

Why this pattern is really an admission, not a workaround: every one of these systems delivers at-least-once, not exactly-once, because exactly-once delivery isn't achievable over an unreliable network (Prerequisites O). Idempotency isn't a patch over a bug — it's the correct, honest design response to that fact.

F. Pattern 5 — Push vs. Pull (Fan-Out Write vs. Fan-Out Read)

The shape of the problem: one piece of content needs to reach many downstream viewers/consumers — and there's a real choice between doing that work once, in advance, at write time (push / fan-out-on-write) versus doing it fresh, on demand, at read time (pull / fan-out-on-read).

ApproachBest WhenWhere Used
Push (fan-out on write)Read volume vastly exceeds write volume, and readers have a typical/bounded fan-out (most users' feed)News Feed's default path (P3) — a new post is proactively written into each follower's feed
Pull (fan-out on read)The write side has an extreme, atypical fan-out (a celebrity with millions of followers) where pushing to every follower at write time would be prohibitively expensiveNews Feed's celebrity-account exception (P3) — merged into the reader's feed at read time instead

The general lesson: push and pull aren't competing answers to the same problem — they're each correct for a different shape of fan-out, and a mature design (as News Feed's does) uses both simultaneously, choosing per-case rather than picking one globally.

G. Pattern 6 — Shard by the Natural Ownership Key

The shape of the problem: a dataset is too large or too write-heavy for one machine, and needs to be split (sharded, per Prerequisites C) across many — but the wrong shard key forces most requests to fan out across many shards instead of hitting just one.

The fix: pick the shard key that matches the boundary within which data is almost always accessed together — the entity that "owns" a self-contained slice of the problem.

Where It AppearedShard KeyWhy That Key
Rate Limiter (P2)User/client ID (via consistent hashing)A rate-limit decision only ever needs that one client's own counters
Ticketmaster (P8)event_idAll seats, holds, and orders for one event are always accessed together; contains one viral on-sale's load to its own shard
Google Docs (P9)doc_idAll operations for one document must be sequenced by a single authority to have any well-defined order at all
Job Scheduler (P10)job_idA job's schedule, executions, and dependency edges are always evaluated as a unit
Real-Time Leaderboard (P19)Segment key (per-game/region/time-window)Score updates and rank queries within one segment are always evaluated together; keeps one viral game's write volume off every other segment's shard
Search Autocomplete (P13)Query prefix (first one to two characters)The same ownership-boundary reasoning applied to a tree's own structural branches instead of a row's primary key — one shard owns everything rooted under "a"-"f," another "g"-"m," and so on
Payment System (P15)merchant_idAll of one merchant's ledger transactions are always accessed together; keeps one merchant's volume from contending with another's
Distributed Message Queue (P20)Partition count and key-hash, sized deliberately upfrontPartition count isn't casually resizable the way a stateless service's instance count is — getting the shard key and its stability right matters more here than almost anywhere else this pattern appears

H. Pattern 7 — Connection Routing at Scale

The shape of the problem: a fleet of servers each hold a slice of long-lived, stateful connections (WebSockets, per Prerequisites K) — and a message meant for a specific connected user has to somehow find whichever specific server currently holds that user's connection, out of potentially thousands of servers.

The fix: a shared, fast lookup table (user_id → server_id) combined with a pub/sub broker layer — a sender's server looks up the owning server, then publishes to a channel that server is subscribed to, decoupling "who has this connection right now" from "how do I actually deliver to them."

Where It AppearedWhat's Being Routed
Chat System (P4, Deep Dive N)A message, to whichever connection server holds the recipient's WebSocket
Google Docs (P9, Deep Dive S)A document's operations, to whichever coordinating server currently owns that doc_id

I. Pattern 8 — Leader Election for Singleton Responsibilities

The shape of the problem: some function must be performed by exactly one active instance at a time — zero active instances means the function silently stops happening; more than one active instance means duplicated, potentially harmful work.

The fix: lease-based leader election (Prerequisites F) — instances race for a TTL'd lock representing the leader role; the winner renews it periodically; if it stops renewing, another instance takes over automatically.

Where It AppearedThe Singleton Responsibility
Job Scheduler (P10, Deep Dive M)Deciding which jobs are due to trigger right now — the central, defining example of this pattern in the document

Why this is distinct from Pattern 1 (the check-then-act race): Pattern 1 is about two requests racing over a piece of data; this pattern is about ensuring exactly one instance is even allowed to act at all for an entire category of decisions — the granularity is a whole role, not a single record.

J. Pattern 9 — Narrow Strong-Consistency Islands

The shape of the problem: a system is, by default, eventually consistent almost everywhere (because that's simpler, cheaper, and more available) — but one specific piece of state genuinely cannot tolerate staleness or ambiguity without producing an actual correctness failure a user will notice.

The fix: default to eventual consistency everywhere, and deliberately carve out the smallest possible piece of state that needs strong consistency, giving only that piece the more expensive guarantee.

Where It AppearedThe Strong-Consistency IslandEverything Else, Eventually Consistent
URL Shortener (P1)Short-code uniqueness at creation timeThe redirect-serving path itself
Ride-Sharing / Uber (P5)A single driver-rider assignmentDriver location updates, ETA estimates
Ticketmaster (P8)A seat's hold/sold status — the most strongly-consistency-weighted example in the documentThe browse/seat-map display
Job Scheduler (P10)The leader lock itselfExecution history/status visibility
Payment System (P15)The double-entry ledger write itself — the strongest-consistency requirement in the documentNotifications, async reconciliation jobs, webhook processing
Notification System (P12)The dedup check itself — a single atomic operation against a store that guarantees a check-and-claim never racesDelivery status, retry bookkeeping

Another instructive exception — Distributed Key-Value Store (P18): rather than carving out one fixed strong-consistency island, it exposes the N/R/W quorum knobs directly to the caller, so any key can be read/written at whatever point on the consistency/availability spectrum that specific use case needs — the island isn't fixed by the system, it's chosen per-request.

The instructive exception — Google Docs (P9): its consistency model isn't "eventual by default, with one strong island" — it targets strong eventual consistency for content itself (all replicas converge, guaranteed, regardless of arrival order) precisely because there's no single "seat" to protect; every concurrent edit must be preserved and reconciled, never rejected. Worth noticing this is a genuinely different shape than Pattern 9's more common form, not a variation of it.

A third distinct shape — Distributed File Storage (P14): not a strong-consistency island (there's no single scarce resource being contended for — no seat, no leader lock) and not quite Google Docs's strong eventual consistency either — it's offline-tolerant, with conflicts surfaced to the user via a conflicted copy rather than silently merged or silently discarded. Worth keeping this as a third named shape rather than folding it into either of the other two.

K. Pattern 10 — Reusing Rate Limiting as a Cross-Cutting Concern

The shape of the problem: a system needs to bound how fast a given actor can perform some action — not as the main subject of the design, but as a supporting concern layered on top of something else entirely.

The fix: the token-bucket/sliding-window mechanisms designed once, in full, for Rate Limiter (P2) get reused directly wherever this need resurfaces elsewhere, rather than being reinvented per-problem.

Where It AppearedWhat's Being Bounded
Ticketmaster (P8, Deep Dive S)Reservation attempts per user/IP, as part of bot/scalper mitigation
Notification System (P12, Deep Dive Q)Notifications per user per channel, to prevent notification storms; batched into a digest above the limit rather than just dropped
Search Autocomplete (P13)Queries per client, to bound the damage a malicious or buggy client hammering a rare, expensive prefix can do to the shared origin tier

The lesson: recognizing "this is just rate limiting again" and pointing back to an already-solved problem, rather than re-deriving a bespoke throttling mechanism from scratch, is itself a signal of strong pattern recognition in an interview.

L. Pattern 11 — Consistent Hashing for Elastic Ownership

The shape of the problem: a fleet of servers each "own" some slice of a much larger keyspace (shards, connections, documents) — and the fleet needs to grow or shrink without triggering a massive, disruptive reshuffling of ownership every time.

The fix: consistent hashing (Prerequisites J) — map both servers and keys onto a ring, so adding or removing a server only reassigns the small slice of keys immediately adjacent to it.

Where It AppearedWhat's Being Elastically Assigned
Rate Limiter (P2)Which shard owns a given client's rate-limit counters
Chat System (P4)Which connection server a user's session lands on (in combination with Pattern 7's routing table)
Google Docs (P9, Deep Dive S)Which coordinating server owns a given active document
Distributed Cache (P11, Deep Dive N)Which cache shard owns a given key — the ring is the primary partitioning mechanism for the whole design, not an incidental detail
Distributed Key-Value Store (P18, Deep Dive M)Which node (and, via the preference list, which replica set) owns a given key — the deepest treatment of this pattern in the document, including virtual-node count trade-offs
Distributed File Storage (P14)Which chunk-store node owns a given chunk — an unusually clean case, since content-hash keys are already uniformly, pseudo-randomly distributed, with no hot-range concerns to design around

M. Master "What to Use When" Decision Table

A condensed, symptom-first index into everything above — read down the left column for whatever problem you're facing, and land on the pattern and mechanism to reach for.

Symptom / QuestionReach ForPattern
"Two requests could both act on the same stale read"Atomic conditional update / single Redis command or Lua scriptB — Check-Then-Act Race
"This request has a slow part the user doesn't need to wait for"Async queue + background workerC — Hot Path Offloading
"This entity has a small record plus a large binary payload"Metadata in DB, bytes in blob/object storageD — Metadata/Blob Split
"A retried request might repeat a real-world side effect"Client-generated idempotency key, checked server-sideE — Idempotent Retries
"One write needs to reach a huge, variable number of readers"Push (fan-out on write) normally; pull (fan-out on read) for extreme outlier fan-outF — Push vs. Pull
"This dataset needs to be split across machines"Shard by the key that matches the natural access-together boundaryG — Shard by Ownership Key
"A message needs to reach a specific user's persistent connection"Shared session store (user_id → server_id) + pub/sub brokerH — Connection Routing
"Exactly one instance must perform this function, never zero, never two"Lease-based leader electionI — Leader Election
"Most of this can be eventually consistent, but not this one specific field"Default to eventual consistency; isolate one narrow strong-consistency islandJ — Strong-Consistency Islands
"We need to throttle some actor's request rate, as a side concern"Reuse token bucket / sliding window, don't reinventK — Rate Limiting Reuse
"A fleet of servers needs to grow/shrink without mass reassignment"Consistent hashing ringL — Consistent Hashing

N. How to Use This in an Actual Interview

The value of this catalog isn't reciting pattern names — it's the speed and confidence it gives you when a new, unfamiliar-sounding prompt turns out to contain a familiar shape. When an interviewer describes a new problem, listen for which symptom in Section M's table is actually present, and reach for that mechanism directly rather than re-deriving it from first principles under time pressure. Just as importantly, listen for when a pattern doesn't apply — Section C's callout on Ticketmaster's payment flow is a reminder that these are judgment tools, not checklists to apply mechanically regardless of context. The strongest signal you can give an interviewer is naming the pattern, explaining why it fits this specific case, and — just as tellingly — explaining why a superficially similar pattern would be the wrong fit here.