Keentune

System Design curriculum

31 chapters
·
292 concepts
·
free
Everything the adaptive question bank can teach and test in System Design, from foundations through advanced practice. Work through it in order, or start practising and let the questions find your level.
New here? Read the System Design guide
A free 16-minute primer — the mental model, the mistakes beginners make, and what to practise first.
A. Foundations of distributed design
a client sends requests to a shared server, and that shared server is the contended resource every design must size and protect
a request that carries all its own context can be served by any instance, which is what makes a tier cloneable and disposable
the web, application and data tiers saturate on different resources and are therefore sized separately
any component without a redundant peer sets the ceiling on the whole system's availability, whatever the rest is built from
assuming a reliable network, zero latency, infinite bandwidth and one administrator is the root of most outages
a remote call can fail with no answer at all, a state a local function call never produces
a timeout does not say whether the work happened, so the only safe reaction is an idempotent retry
RAM ~100ns, SSD ~100µs, same-datacenter round trip ~0.5ms, cross-continent ~100ms; each step is a different design
work per second and time per request are separate goals, and batching buys the first by spending the second
a pipeline's throughput equals its slowest stage's, so optimizing any other stage changes nothing
B. Capacity estimation and the math of scale
daily users × actions per user ÷ 86,400 gives average QPS, and the design must survive the peak, not the average
real traffic peaks at roughly 2–5× its daily mean, so provisioning to the mean schedules an outage
a 100:1 read-heavy workload is a caching problem; a write-heavy one is a partitioning problem
rows × bytes per row × retention × replication factor, plus index and metadata overhead
bandwidth is QPS × payload size, and outbound bytes are the metered, expensive direction
concurrency equals arrival rate × latency, so 1,000 QPS at 100ms needs 100 requests in flight
waiting time scales with 1/(1−utilization), so latency explodes somewhere above ~80% load
the serial fraction caps total speedup no matter how many machines are added
contention plus cross-node coherency makes throughput peak and then fall as nodes are added
capacity must absorb the largest failure domain's share of load at peak, not merely the peak itself
C. Scaling axes and compute topology
a bigger machine has a hard ceiling and stays one failure domain; more machines remove both limits and add coordination
the x-axis clones the whole app, the y-axis splits it by function, the z-axis splits it by data
nodes sharing no mutable state scale close to linearly, and any shared resource becomes the new ceiling
scale on queue depth or request rate, which lead demand, not on latency, which lags it until users already hurt
instances take minutes to boot and warm while a spike arrives in seconds, so autoscaling is not a burst-absorption strategy
terminating an instance without draining in-flight requests drops them, so every scale-in needs a drain period
no capacity planning and per-request billing, paid for in cold starts and downstream connection-pool pressure
reads scale by adding replicas, writes scale only by partitioning, and conflating the two misroutes the whole design
one global sequence, lock or leader caps the system regardless of how wide everything else scales
D. Load balancing and traffic distribution
L4 forwards by connection and is cheap; L7 parses the request and can route, retry and rewrite at a per-request cost
equal request counts are unequal work when request costs differ by orders of magnitude
routing to the backend with the fewest in-flight requests adapts automatically to uneven request cost
sampling two random backends and picking the lighter one gets near-optimal balance with no global state
hashing the request key to a backend keeps that backend's cache warm across requests
an explicit probe endpoint versus inferring health from the errors real traffic already sees
a health handler that returns 200 without touching its dependencies keeps a broken backend in rotation
each client connects to a bounded subset of backends so connection count does not grow as clients × servers
sticky sessions unbalance load and turn one backend's death into user-visible state loss
resolver caching and TTL make DNS a slow, uneven way to shift or drain traffic
E. CDNs, the edge and network delivery
distance costs time no software can recover, which is the entire reason a point of presence exists
origin-pull fills lazily on the first miss and pays it once; push pre-positions content and pays storage everywhere
the origin's headers, not the CDN, decide what may be cached and for how long
an ETag or Last-Modified check returns 304 and saves the bytes, but still pays the round trip
cookies, query strings and Vary headers fragment one object into many and quietly destroy the hit rate
a content-hashed filename makes invalidation unnecessary because a new version is a new object
a mid-tier that collapses misses from many points of presence into a single origin request
for small objects the TLS handshake dominates transfer, which is what keep-alive and 0-RTT resumption remove
one address announced from many sites, with routing rather than DNS choosing the nearest entry point
F. Caching and invalidation
the application checks the cache, loads on a miss and populates it, so a cold or empty cache is always safe
the cache sits in the data path, so it never serves stale data and every write pays for it
acknowledging at the cache and flushing later gives the fastest writes and a real window of data loss
expiry is the invalidation strategy that needs no messaging, at the price of a known staleness window
LRU, LFU, and the sequential scan that evicts an entire LRU working set in one pass
a 95% hit rate still sends one request in twenty through, and the origin must be sized for that miss stream
one hot key expiring sends every concurrent request to the origin in the same instant
coalescing concurrent misses into one fetch, or refreshing just before expiry, removes the stampede
caching "not found" stops a missing or malicious key from becoming unbounded origin load
one write must expire every derived key, and the one you forget serves wrong data until its TTL
G. Partitioning and sharding
ordered keys make range scans cheap, and a monotonically increasing key hot-spots the newest shard
hashing spreads load evenly and destroys the ability to scan a range
mapping nodes and keys onto a ring moves only about 1/N of keys when a node joins or leaves
many tokens per physical node smooth the lopsided ownership a small ring produces
a key absent from the common predicate turns every read into a scatter-gather
an even hash still concentrates load when one key is popular, which is fixed by splitting or replicating that key
a request fanned out to N shards waits for the slowest of N, so the tail becomes the common case
split and migrate with dual writes and a cutover; a big-bang rehash requires downtime
a local index needs a fan-out read, a global one needs a cross-shard write
losing single-node atomicity is the real cost of sharding, not the routing code
H. Replication
one node accepts writes and the others follow, which is the simplest topology that is obviously correct
synchronous replication costs write latency and availability, asynchronous costs durability at failover
waiting for any k of n followers bounds write latency while still surviving k−1 losses
a follower can be arbitrarily behind, and lag grows exactly when write load is heaviest
a user must see their own write, which means routing them to the leader or to a replica past their write version
a second read must never show an older state than the first, which random replica routing breaks
promoting an asynchronous follower silently discards writes the client was already told succeeded
two nodes each believing they lead; a monotonically increasing fencing token makes the stale one harmless
concurrent writes accepted in two places must be detected and merged, because one of them is otherwise lost
leaderless systems converge by fixing stale replicas on read and by comparing replicas in the background
a peer accepts a write on behalf of a node that is down and forwards it on recovery, trading durability risk for write availability
I. Consistency models, CAP and PACELC
every operation appears to take effect at one instant between its call and its return
serializability orders whole transactions, linearizability orders single-object operations in real time
both guarantees at once, and the coordination that makes it the most expensive model to provide
causally related operations are seen in the same order everywhere, while concurrent ones may differ per observer
replicas converge once writes stop, with no promise at all about when
read-your-writes, monotonic reads, monotonic writes and writes-follow-reads are useful without global consistency
during a network partition a system must choose consistency or availability; "CA" is not an operating mode
and with no partition, the same system still trades latency against consistency on every request
W+R>N alone does not give linearizability once writes are concurrent or partially applied
a merge that is commutative, associative and idempotent converges with no coordination at all
J. Consensus and leader election
a set of nodes agrees on one value and never un-agrees, despite crashes, delays and lost messages
any two majorities share at least one node, which is why 2f+1 replicas tolerate f failures
every message carries a term number, and a message from a stale term is rejected outright
a follower whose election timer expires becomes a candidate and needs votes from a majority to lead
randomizing the timer breaks the symmetry that otherwise produces repeated split votes
an entry is committed once a majority has stored it, and only a committed entry may be applied
a leader never rewrites its own log; followers delete conflicting suffixes until they match the leader's
a time-bounded lease lets the leader answer reads locally, trading a clock assumption for a round trip
no deterministic protocol solves consensus in a fully asynchronous system with one crash, and timeouts are the practical escape
tolerating nodes that lie rather than merely stop requires 3f+1 replicas, not 2f+1
K. Clocks, time and ordering
a wall clock can jump backwards when corrected, so elapsed time must be measured with a monotonic clock
synchronized machines still differ by milliseconds, so cross-machine timestamps cannot order events
the only ordering a distributed system genuinely has is causal, established by messages, not by timestamps
a single counter gives an order consistent with causality but cannot distinguish concurrent from ordered
one counter per node detects the concurrency a Lamport clock hides, at O(nodes) metadata per value
resolving conflicts by timestamp is deterministic and silently discards one of two real writes
physical time nudged by causality yields timestamps that are both human-readable and causally correct
waiting out the clock-error bound before committing is what buys externally consistent timestamps
Snowflake-style ids embed a timestamp for rough sortability and break when the clock moves backwards
L. Transactions and sagas across services
read committed, repeatable read, snapshot and serializable are each defined by the anomaly they still permit
two transactions read the same state and write different rows, together breaking an invariant snapshot isolation cannot see
a prepare round followed by a commit round gives atomicity across independent resources
participants hold locks until the coordinator returns, which is why 2PC is avoided in high-scale designs
a business transaction becomes a sequence of local transactions, each with a compensating action
the intermediate state was visible and may already have been acted on, so compensation is semantic, not a rewind
one coordinator that knows the whole workflow versus services reacting to each other's events
writing the database and publishing the event as two operations will eventually do one and not the other
the event row is written in the same transaction as the state change and relayed by a separate process
tailing the database's replication log turns committed state into an ordered, replayable event stream
M. Messaging, queues and delivery semantics
a queue converts a spike the consumer cannot serve live into a backlog it can drain at its own rate
a queue delivers each message to exactly one consumer, a topic fans the same message out to every subscriber
you choose between losing messages and duplicating them; there is no third delivery guarantee
end-to-end correctness comes from deduplication plus an idempotent consumer, never from the delivery layer alone
an unacknowledged message reappears, so a consumer slower than the timeout manufactures duplicates
total ordering costs a single partition, so ordering per key is the guarantee real systems offer
partitions are leased to consumers, and every rebalance pauses consumption for the whole group
a log retains messages and is read by offset so it can be replayed; a queue deletes on acknowledgement
a poison message must be moved aside, or it blocks its partition forever and stalls the pipeline
one slow message stalls everything behind it in the same ordered stream
N. Event-driven architecture, CQRS and event sourcing
a thin event forces a callback to the producer, a fat event carries state that may be stale on arrival
a command may be rejected, an event states something that already happened and cannot be refused
the write model and the read models are separate schemas, each shaped for its own access pattern
a projection is eventually consistent, so the interface must show pending state rather than pretend the write landed
the append-only event log is the source of truth and current state is a fold over that log
replaying from the first event grows without limit, so periodic snapshots are what cap recovery time
schema changes must stay backward-compatible because history can never be rewritten
a new read model is built by replaying history, which is the main practical payoff of event sourcing
pure choreography leaves no single place describing the end-to-end flow, turning debugging into log correlation
O. API design and service boundaries
URLs name resources and verbs carry the action, which is what lets generic caches and proxies do useful work
GET, PUT and DELETE are defined as idempotent, so an intermediary may safely repeat them
a GET with side effects breaks every cache, proxy and prefetcher in the path
4xx tells a client not to retry, while 5xx and 429 tell it to retry later
offset paging skips and repeats rows as data shifts and slows on deep pages; a cursor is stable and cheap
add fields, never repurpose them, and run both shapes simultaneously through a migration
binary framing on HTTP/2 with generated stubs buys speed and streaming, and costs browser reach and human debuggability
one flexible query removes over-fetching and hands the client the ability to write an unbounded expensive query
exactly one service writes a given table; a second service reading it directly rebuilds the monolith with a network in the middle
authentication, rate limiting, routing and TLS terminate once at the edge instead of in every service
P. Storage engines, indexes and search
B-trees update pages in place and favor reads; LSM trees append and merge, favoring writes
compaction rewrites the same data repeatedly, which is the disk cost an LSM pays for fast writes
an LSM read may consult several levels, which is exactly what a Bloom filter is there to cut
a probabilistic set with false positives but no false negatives, sized by the false-positive rate you accept
durability comes from an append-only log written before the data page, not from the page write itself
a write acknowledged from the page cache survives a process crash but not a power loss
the clustered index defines physical row order, a secondary index stores a pointer back to it
row storage serves point reads and writes, column storage serves wide aggregate scans and compresses far better
search tokenizes text into term-to-document postings and ranks by relevance instead of filtering by predicate
a search index is a denormalized, eventually consistent copy fed from the primary database
Q. Rate limiting, load shedding and backpressure
a rate limit must sit in front of the thing that actually saturates, not at whichever layer was easiest
tokens refill at a fixed rate and accumulate up to a cap, which is what permits a bounded burst
a constant-rate drain smooths output completely and rejects whatever the bucket cannot hold
cheap, and wrong at the boundary: two adjacent windows can pass twice the limit back to back
an exact log of timestamps is precise and memory-hungry; a weighted counter approximates it for a fraction of the cost
per-node limits drift as the fleet resizes, and a shared counter costs a round trip on every request
dropping low-value requests early keeps the remaining ones inside their SLO instead of failing everything slowly
labeling requests by priority is what makes it possible to shed batch traffic and keep interactive traffic
propagating "slow down" to the producer is the only bounded alternative to an ever-growing queue
an unbounded queue converts overload into invisible latency and then an out-of-memory crash
R. Timeouts, retries and circuit breakers
a call with no timeout holds its thread and connection until the process dies
passing the remaining time budget downstream stops work whose caller has already given up
a dependency failing half its requests receives multiples of its normal traffic exactly when it is weakest
doubling the wait spreads retries out; a fixed short interval is what turns a blip into an outage
randomizing the delay stops every client from retrying in the same instant after a shared failure
capping retries as a fraction of total requests bounds amplification no matter how many clients are retrying
a client-supplied key lets the server recognize a retry and return the original result instead of repeating the work
after enough failures, fail immediately for a while so the failing dependency gets the quiet it needs to recover
separate pools per dependency stop one slow dependency from consuming every thread and connection
a cached, partial or default answer beats an error for the whole page when one dependency is down
S. Failure modes and their signatures
a dead replica's load moves to the survivors, overloading them in turn until nothing is serving
many clients waking, reconnecting or expiring at the same moment produce a synchronized spike
the system stays down after the trigger is gone because the retries it induced now sustain the overload
a metastable system does not recover by restarting; the load must be shed until the queue drains
load concentrated on one partition while fleet-wide utilization graphs look comfortable
a component that is slow or subtly wrong passes health checks while users see errors
a shared dependency, config push or expiring certificate takes out supposedly independent replicas together
memory pressure raises latency, which raises queue depth, which raises memory pressure
partitioning users into independent cells bounds any one failure's blast radius to one cell
giving each tenant a random subset of nodes makes it unlikely that any two tenants share their whole subset
T. Observability, SLOs and error budgets
a service level indicator is a measured ratio of good events to valid events, from the user's side
a target without a time window and a measurement method is not a commitment
the external promise carrying consequences, deliberately looser than the internal SLO
one hundred percent minus the SLO is the failure the team is allowed to spend on releases and experiments
99.9% permits about 43 minutes of downtime per month, and each further nine costs an order of magnitude more
an average hides the tail, and the tail is what the busiest users actually experience
a request touching 100 backends meets a one-in-a-hundred slow backend almost every time
sending a duplicate to a second replica after a short delay cuts the tail for a few percent extra load
metrics aggregate, logs explain one event, traces connect one request across services
paging on how fast the error budget is being consumed detects fast outages and ignores harmless blips
U. Deployment and safe change
shipping the binary and enabling the behavior are separate events joined by a feature flag
replacing instances in batches preserves capacity and guarantees two versions run simultaneously
a full parallel environment buys an instant switch and an instant rollback at roughly double the infrastructure
comparing the canary against a same-age control group separates the release's effect from time-of-day effects
a canary too small to reach statistical significance detects nothing and gives false confidence
turning a change off in seconds beats any redeploy as an incident response
add the new column, write both, backfill, read the new, and only then remove the old
during any rollout old and new code must both handle the shared schema and in-flight messages
a dropped column or deleted topic cannot be undeployed, so they come last and separately
V. Multi-region, disaster recovery and data locality
zones fail independently within one region's latency budget; regions are independent but a round trip apart
a standby is simpler and idle, active-active uses the capacity and doubles the consistency problem
how much data you may lose and how long you may be down are separate targets with separate prices
replication faithfully copies a deletion or a corruption, and only a backup can undo one
an unrestored backup is a hypothesis, and the number that matters is how long a restore takes
a legal requirement to keep records inside a jurisdiction constrains the replication topology directly
pinning each record's writes to one region keeps cross-region conflicts out of the design entirely
promoting another region either loses recent writes or refuses writes until the primary returns
backup-and-restore, pilot light, warm standby and multi-site trade steady-state cost against recovery time
W. Data modelling, database selection and indexes
the trade is a fixed relational schema with joins against a flexible one that pushes joins into the application
third normal form removes update anomalies by storing each fact once, at the cost of joins on every read
duplicating a fact buys read speed and pays for it with every write having to update several copies
validating at write time makes the reader's life predictable; deferring it makes ingestion cheap and every reader responsible
atomicity, consistency, isolation and durability are four separate promises, and systems relax them independently
a transaction applies wholly or not at all, so a partial failure leaves no half-written state behind
keeping several versions of a row lets a reader see a consistent snapshot without blocking a concurrent writer
the access pattern is a single known key, which is what makes the store trivially partitionable
a self-contained document suits data read as a unit, and makes cross-document queries the expensive case
a partition key plus a clustering key optimizes for wide rows read in ranges, not for ad-hoc queries
the fit is traversal depth: a query following many hops is where a graph engine beats repeated joins
append-mostly, time-ordered data with retention and downsampling built into the storage layer
a hash index answers equality only; a range scan needs an ordered structure
an index on (a, b, c) serves a leading prefix of those columns, not an arbitrary subset
when the index holds every column the query reads, the engine never touches the table itself
an index only pays off when it eliminates most rows; on a low-cardinality column a scan wins
a warehouse is modelled for wide analytical scans, not for the small transactional writes an application makes
transforming before loading protects the destination's schema; loading first moves the work to where the compute is
nearest-neighbour search trades exactness for latency, because an exact scan of high-dimensional vectors does not scale
vectors written by one model cannot be compared with vectors from another, so a re-embed is a migration
X. Service discovery, meshes and the request path
instances come and go, so callers need a current address list rather than a hard-coded host
instances register and heartbeat, and the registry expires whatever stops reporting
choosing the instance in the caller removes a hop and moves the routing logic into every client
a proxy beside each instance moves retries, mTLS and telemetry out of application code
running logic at the edge cuts round-trip time, and constrains what state that logic can reach
browser, CDN, gateway and application caches each answer a different miss, and each has its own invalidation problem
Y. Stream and batch processing
a stream processes unbounded data continuously; a batch job processes a bounded set on a schedule
batch is the right answer when the work is expensive, the data is complete, and latency does not matter
an unbounded stream has no natural end, so aggregation needs a window to define what "all of it" means
tumbling windows never overlap, sliding windows do, and session windows are cut by a gap in activity
the time a thing happened and the time it arrived diverge, and only one of them gives reproducible results
a watermark is the system's claim about how late data can still be, which is what lets a window ever close
a batch layer and a speed layer answer the same question twice, and the cost is two implementations to keep in step
treating everything as a stream and replaying the log removes the second implementation
Z. Design building blocks and the interview method
the first move is establishing scope and scale, because every later decision depends on numbers nobody has stated yet
features are one axis; latency, availability, durability and cost are the axis that decides the architecture
the smallest design that meets the stated load is the starting point, and each addition needs a reason
a URL shortener maps a generated key to a target, and the key strategy decides collisions and guessability
a prefix tree answers autocomplete because the shared prefix is the path
encoding a location as a string makes proximity a prefix comparison, with edge cases at cell boundaries
recursively subdividing space keeps queries proportional to local density rather than total points
a sorted set gives rank and range in logarithmic time, which is what a top-N board needs
cardinality within a few percent, in kilobytes, when an exact distinct count would need every value
frequency estimates that may overcount but never undercount, sized by the error you accept
online status is high-churn state fanned out to many watchers, which is what makes it costly rather than hard
large media goes in object storage with a reference in the database, never in the row itself
moving cold data to cheaper, slower storage trades retrieval latency for cost
AA. Case studies at scale
the client picks a quality level per segment from measured bandwidth, so playback degrades instead of stalling
a crawler must respect robots rules, rate-limit per host, and deduplicate URLs it has already seen
a large file is split into fixed chunks, replicated across nodes, with metadata held separately
high-volume events are buffered, aggregated in windows and written as rollups rather than rows
a job scheduler must survive duplicate triggers, missed runs and a worker dying mid-job
the models run from a shared table with a tenant column to a database per tenant, trading isolation against cost
a managed service buys operational load off your team and constrains what you can tune
AB. Security, trust boundaries and compliance
volumetric attacks are absorbed upstream at the edge, because the origin is exactly what must not be reached
pattern-based filtering in front of the app catches known attack shapes and cannot understand intent
credentials belong in a system that issues, rotates and audits them, not in config or an image
both sides presenting certificates authenticates the CALLER, which a network boundary alone never does
the network position of a request grants it nothing; every call is authenticated and authorized on its own
the two defend different attacks, so having one is not having the other
a deletion requirement is hard precisely where data was duplicated: backups, logs, caches and derived stores
AC. Web protocols and delivery basics
a name is resolved to an address through a cache hierarchy, and the TTL decides how fast a change takes effect
a cron job runs work on a timer, and at scale the questions are overlap, missed runs and where it runs
a pre-production environment catches what a test cannot: real configuration, real data shapes, real dependencies
compressing a response trades CPU for bytes, and is worth it exactly when the network is the constraint
a persistent bidirectional connection lets the SERVER initiate, which request/response cannot
holding a request open until data arrives approximates push over plain HTTP, at the cost of a held connection
handing the client a scoped URL keeps large uploads off the application server entirely
AD. Advanced coordination and replication
a suspicion level derived from heartbeat history adapts to the network, where a fixed timeout cannot
writes enter at the head and reads leave from the tail, which gives strong consistency with a simple failure story
markers flowing with the data let a distributed job snapshot itself without stopping
a computation that is monotone can run without coordination, which is a property of the program rather than the infrastructure
agreeing the transaction order first makes execution deterministic and removes the need to abort on conflict
AE. Infrastructure and operational practice
servers are replaced rather than modified, so the running fleet always matches what was built
declaring infrastructure in version control makes an environment reproducible and a change reviewable
packaging the application with its dependencies makes what ran in test the same artifact that runs in production
config in the environment, stateless processes and disposable instances are what make a service safe to scale horizontally
Keentune is not affiliated with or endorsed by the organizations whose documentation informs these maps.
All about System Design practice
Also on your phone
All exam, test, and product names and trademarks are the property of their respective owners and are used here for identification and reference only. Keentune is independent study practice — not affiliated with, authorized, or endorsed by any of these organizations.
© 2026 SportaApp LLC