anishfyi.com Upskill DSA Math System design

upskill / system design / read all, drill eight, own one

System design

Boxes are cheap. The interview is won at the tradeoff.

The method: read every answer once for breadth, whiteboard eight of them cold, and own one flagship end to end. The flagship is VoicerAI, at the bottom of this page: a real system with real numbers. At a YC startup, the system you actually built beats the one you memorized.

0 / 38

01The frame

Four moves, every time

Forty five minutes, one shape. Only the deep dive changes between questions. The algorithms and data structures underneath all of this live in the DSA track.

move 1

Requirements and scale

Functional first (what it does), then non-functional (latency, availability, consistency). Then do the envelope out loud: DAU, QPS with a 10x peak, storage per year, cache size. The numbers live in the estimation drill. Two minutes of arithmetic buys you the whole design.

move 2

High-level design

Draw the boxes, name the arrows, then walk one request through end to end. If you cannot narrate a single write and a single read across your own diagram, the diagram is decoration.

move 3

Deep dive the hard part

The interviewer picks one box, or you steer to your strongest. Every card below names its hard part so you know where the follow-up is coming from. Steering is allowed: "the interesting problem here is the fanout, want me to go deep on that?"

move 4

Wrap

Failure modes (what breaks first), bottlenecks (what saturates first), and what you would build first if this were week one. That last answer separates people who ship from people who diagram.

fundamentalsthe twelve moves

Load balancing

L4 spreads connections, L7 reads the request and can route on it. Round-robin by default, least-connections for uneven work, consistent hashing when servers hold session or cache state. Health checks make it your failover mechanism too.

Caching

Levels: browser, CDN, gateway, application, database. Cache-aside is the default pattern; invalidate with TTL plus explicit purge on write, and say the staleness window out loud. Stampede protection: a per-key lock or probabilistic early refresh, so one request rebuilds the value instead of ten thousand.

Sharding and partitioning

Hash sharding spreads load evenly but kills range queries; range sharding keeps them but invites hot spots. The shard key is the real decision: pick it so one entity's data lands together and no entity dominates. Resharding hurts, which is why consistent hashing exists.

Replication

Leader-follower: writes to one node, reads scale across copies. Async replication means followers lag, so a read after a write can travel back in time. Failover is the hard part: promote via consensus, and fence the old leader so you never get two.

CAP, honestly

During a partition you choose: refuse requests (consistency) or serve possibly stale data (availability). Not a slogan, a per-operation decision: a payment check picks C, a like counter picks A. Say which operations give up what.

Consistency levels

Strong: everyone sees every write instantly, paid for in latency and availability. Read-your-writes: you see your own updates, others can lag; this is what products usually need. Eventual: replicas converge, order unspecified. Most designs mix all three by endpoint.

Queues and backpressure

A queue decouples producer rate from consumer rate and absorbs spikes. Queue depth is your earliest warning signal: bound it, and when it fills either shed load or push back upstream. Unbounded queues turn overload into an out-of-memory crash later.

CDN and edge

Static assets always, with content-hashed immutable URLs so cache invalidation stops existing. Then dynamic reads that tolerate a short TTL. Egress and origin offload is usually the biggest single cost lever in media-heavy systems.

Database indexes

B-tree by default: equality and range in O(log n). Composite index column order matters: it serves prefixes only. A covering index answers from the index alone. Every index taxes every write, so index the queries you have, not the ones you fear.

Rate limiting algorithms

Token bucket: refill at rate r, allow bursts up to bucket size; the usual answer. Sliding window counter: smooths bursts, weights the previous window. Fixed window has the boundary bug: 2x the limit across the reset line. Return 429 with Retry-After.

Idempotency keys

Client sends a unique key per logical operation; the server stores key to result and replays the stored result on retry. This is how at-least-once delivery becomes exactly-once effect. Any endpoint that moves money or sends messages needs one.

Realtime delivery

Websockets: bidirectional, stateful, needs sticky routing; for chat and games. SSE: one-way server push over plain HTTP with auto-reconnect; for feeds and LLM token streams. Long polling is the fallback everywhere. Stateful connections are why realtime systems shard by session.

02Building blocks

The nine primitives

Every product question below decomposes into these. If you can whiteboard the key-value store and the message queue cold, you can derive half of everything else in the room.

URL shortener

testing: key generation, redirect semantics, read-heavy caching

components

client, LB, write API, ID allocator, base62 encoder, KV store, cache, redirect service, click stream

key decisions
  • Base62 of a counter over hashing: no collisions to retry, 7 chars covers 3.5 trillion URLs. Allocate counter ranges per node so the counter is not a single point.
  • 301 vs 302: 301 caches in the browser and kills your analytics; 302 keeps every click on your servers. A business choice, say so.
  • Reads dominate about 100:1, so cache the slug lookup (cache-aside, LRU) and let the DB take misses only.
hard part

One viral slug dwarfing all other traffic: cache plus CDN absorbs it, and expired or deleted slugs must not be re-issued.

Rate limiter

testing: algorithm choice, shared state, failure posture

components

gateway middleware, rules store, Redis counters, response path (429 + Retry-After headers)

key decisions
  • Token bucket when bursts are legitimate; sliding window counter when smoothness matters; fixed window is wrong by 2x at the boundary.
  • Counters in Redis with a Lua script so check-and-increment is atomic across gateway instances; in-process counters only if approximate is acceptable.
  • Fail open or fail closed when Redis dies: open for product endpoints, closed for auth and payments. Decide before the outage.
hard part

The accuracy vs synchronization tradeoff: exact global limits cost a network hop per request; most systems accept small overshoot.

Consistent hashing

testing: whether you know why mod N fails

components

hash ring, virtual nodes, membership service (gossip or ZooKeeper), client library or routing tier

key decisions
  • Servers and keys hash onto the same ring; a key belongs to the next server clockwise. Adding a node remaps about 1/N of keys; mod N remaps nearly all of them.
  • 100 to 200 virtual nodes per server smooths the load imbalance of random placement and lets big machines take more of the ring.
  • Ring ownership: client-side (memcached style) vs every node can route (Dynamo style). The second survives dumb clients.
hard part

Hot keys still land on one node: the ring balances key count, not key traffic. Bounded-load variants and key replication answer that.

Unique ID generator (Snowflake)

testing: coordination-free generation, clock trust

components

per-node generator: 41-bit millisecond timestamp + 10-bit machine id + 12-bit sequence, in one 64-bit int

key decisions
  • Snowflake over UUIDv4: ids are k-sorted by time, so B-tree indexes stay dense; random UUIDs fragment every index they touch.
  • Machine id assigned at boot (config or ZooKeeper); the sequence gives 4096 ids per ms per node with zero coordination after that.
  • Clock rollback: refuse to generate and wait it out rather than risk duplicates. NTP slew, never step, on these hosts.
hard part

What "roughly time ordered" does not promise: cross-node ordering within a millisecond is unspecified, and consumers must not assume it.

Key-value store (Dynamo-style)

testing: the Dynamo paper, quorums, storage engines

components

consistent hash ring, N=3 replication, R/W quorums, vector clocks, hinted handoff, Merkle tree anti-entropy, LSM storage (WAL, memtable, SSTables)

key decisions
  • Quorum tuning: W+R greater than N gives read-your-writes; drop W for write speed, R for read speed, and name what you lose.
  • Conflict resolution: vector clocks surface siblings to the application; last-write-wins is simpler and silently drops data (Cassandra chose it anyway).
  • LSM over B-tree for write-heavy load: sequential writes now, compaction later, bloom filters to save the read path.
hard part

The repair machinery: hinted handoff for short outages, Merkle-tree anti-entropy for long ones, and tombstones that resurrect deletes if compaction outruns repair.

Distributed cache

testing: invalidation, stampedes, hot keys

components

client library, consistent hashing across shards, optional replicas, eviction policy, metrics on hit rate

key decisions
  • Cache-aside as the default; write-through when you need read-after-write; write-back is fastest and loses data on crash, so only for tolerable writes.
  • Stampede protection: per-key mutex or probabilistic early expiry, so an expiring hot key is rebuilt by one request, not a thundering herd.
  • TTL plus explicit invalidation on write, and an honest staleness window stated per data class.
hard part

The hot key: one celebrity key saturates one shard. Replicate the top-N keys or cache them client-local, and know your eviction policy under skew (LRU vs LFU).

Distributed message queue (Kafka-style)

testing: log vs queue, ordering scope, delivery guarantees

components

producers, brokers with partitioned append-only logs, replication with in-sync replicas, consumer groups with offsets, metadata (KRaft/ZooKeeper)

key decisions
  • Log with consumer-owned offsets (Kafka) vs broker ack-and-delete (RabbitMQ): the log gives replay and cheap fanout; the queue gives per-message routing.
  • Partition key sets ordering scope: order holds only within a partition, so key by the entity that needs it (user id, order id).
  • At-least-once plus idempotent consumers beats chasing exactly-once; Kafka's transactional exactly-once stops at its own boundaries.
hard part

Consumer group rebalances stalling consumption, and partition skew from a bad key turning one broker into the bottleneck.

Distributed job scheduler

testing: leases, at-least-once execution, time-based triggering

components

job store, trigger service (indexed next-run scan or timing wheel), lease-based workers, retry policy, dead letter queue, status API

key decisions
  • Workers pull with a lease (visibility timeout): a crashed worker's job simply reappears when the lease expires. Push requires tracking worker health.
  • Exactly-once execution is a myth: promise at-least-once and make job payloads idempotent with an execution key.
  • WHERE next_run <= now with SKIP LOCKED is fine for millions of jobs; a hierarchical timing wheel when the scan becomes the bottleneck.
hard part

The thundering herd at popular cron minutes (everyone schedules on the hour): jitter, sharded scans, and per-tenant fairness.

Object storage (S3-style)

testing: metadata vs data planes, durability math

components

API gateway, metadata service (key to placement), data nodes with immutable chunks, erasure coding, background scrubber and repair

key decisions
  • Split metadata from bytes: the metadata DB, not the disks, is the real scaling problem (flat keyspace, list operations, billions of rows).
  • Erasure coding (say 8+4) over 3x replication: 1.5x storage overhead for eleven-nines durability, paid for in repair bandwidth and CPU.
  • Objects are immutable; overwrite creates a version. That one rule makes caching, replication, and repair all trivial.
hard part

Listing a bucket with billions of keys (prefix sharding of the metadata index) and multipart upload: parts, completion, and orphan cleanup.

03Products

The classics, one twist each

Each of these is a building block plus one twist. Name the twist in the first five minutes: the interviewer wrote the question around it.

Pastebin

testing: can you keep a simple problem simple

components

API, key generator, metadata DB, object store for paste bodies, cache, CDN for public pastes

key decisions
  • Blobs in object storage, metadata in the DB: do not stuff megabyte text bodies into database rows built for kilobytes.
  • Expiry is lazy: check on read, sweep in the background. No per-paste timers.
  • Same shape as the URL shortener, read-heavy: cache popular pastes, CDN the public ones.
hard part

Restraint, and the unglamorous real problem: abuse. Spam and leaked-secret takedowns are the operational load, not the reads.

Web crawler

testing: politeness, dedup at scale, frontier design

components

seed list, URL frontier (priority front queues, per-host back queues), DNS cache, fetcher pool, parser, URL and content dedup, storage

key decisions
  • Politeness by construction: one host maps to one back queue maps to one fetcher, so you cannot hammer a domain even by accident.
  • Dedup cheaply: bloom filter for seen URLs (accept rare false positives), SimHash for near-duplicate pages.
  • Recrawl by observed change rate per page, not on a uniform schedule.
hard part

Crawler traps: calendars and faceted search generate infinite URL spaces. Depth caps, URL normalization, and per-domain budgets.

Search autocomplete (typeahead)

testing: precompute vs query-time work

components

query log gathering, offline aggregation, trie builder with top-k cached per node, sharded serving layer, browser and edge caching

key decisions
  • Precompute top-k per prefix and store it at the trie node (or a flat prefix table): never rank at query time, the p99 budget is about 100 ms including network.
  • Daily offline rebuild plus a small realtime layer for trending queries, merged at serve time.
  • Shard by historical prefix load, not alphabetically: "s" and "x" are not the same size.
hard part

The update path: folding fresh counts into a precomputed structure without rebuild pauses, and filtering offensive suggestions before they trend.

Notification system

testing: multi-channel fanout, reliability without spam

components

API, preference and device-token store, per-channel queues (APNs, FCM, SMS, email), worker pools, rate limiter, dedup, delivery analytics

key decisions
  • A queue per channel: providers fail independently and differ in throughput by orders of magnitude; one shared queue lets SMS lag block push.
  • At-least-once with retries and a DLQ, plus an idempotency key per notification so a retry never double-sends.
  • Per-user rate caps and preference checks in the pipeline: notification fatigue is churn, and the cap is a feature.
hard part

Device token lifecycle: tokens go stale silently, providers throttle you, and delivery tracking is the only way to know either happened.

News feed

testing: fanout-on-write vs fanout-on-read

components

post service, fanout service, follower graph, per-user feed cache (Redis lists of post ids), hydration, ranking, media on CDN

key decisions
  • Fanout-on-write for normal users: push the post id into each follower's cached feed at post time, because reads dominate and must be cheap.
  • Fanout-on-read for celebrities: 100M followers times one write is an outage, so merge their recent posts at request time. The hybrid is the answer.
  • Store ids, hydrate on read, cache only the most recent few hundred entries per user.
hard part

Where the hybrid cutoff sits, and keeping the celebrity merge inside the read latency budget.

Chat (WhatsApp)

testing: stateful connections, ordering, offline delivery

components

websocket chat servers, session registry (user to server), message store (write-heavy wide-column), per-conversation sequence, presence, push notifications

key decisions
  • Long-lived websocket per client; a session registry makes send a lookup plus one hop between chat servers.
  • Order per conversation with a sequence number, never wall clocks: clocks lie, sequences do not.
  • Persist first, then push: offline users get a notification and sync on reconnect via a per-device cursor. Receipts are just messages.
hard part

Multi-device sync (each device is its own cursor) and group fanout, which is why group sizes are capped; end-to-end encryption fights every server-side feature.

Twitter timeline and search

testing: hybrid fanout plus a near-realtime index

components

tweet ingestion, fanout service, timeline caches, follower graph, realtime inverted index sharded by time and term, ranking, trends pipeline

key decisions
  • Hybrid fanout exactly as in the news feed: push for most, pull and merge for the celebrity tail.
  • Time-sharded search index: most queries want recent tweets, so the hot shards stay small and in memory; a tweet becomes searchable in seconds.
  • Trends are approximate: count-min sketch over a sliding window, not exact counters.
hard part

Doing both at once: the write path that feeds timelines and the index must not contend, so both hang off the same ingestion log.

Instagram

testing: media pipeline and feed in one design

components

upload service, object store, async processing queue (resize, transcode), CDN, sharded metadata DB, hybrid-fanout feed, graph service

key decisions
  • Accept the original, return fast, process async: thumbnails and renditions come from a queue, never the upload request path.
  • Shard metadata by user id so a profile is one shard; Snowflake-style photo ids sort by time within the shard for free.
  • Immutable, content-addressed media URLs: infinitely cacheable, CDN does the serving.
hard part

Feed generation over a skewed follow graph, and storage economics: old photos need colder tiers without breaking those immutable URLs.

Dropbox / Google Drive

testing: sync protocol, dedup, conflict handling

components

client with local index and watcher, content-defined chunker (about 4 MB blocks), block store, metadata service (namespace, file to block list, versions), change notification via long poll

key decisions
  • Content-hash addressed blocks: dedup across users, delta sync (only changed blocks travel), and resumable transfers, all from one decision.
  • Metadata and blocks in different stores: small consistent relational data vs big immutable cheap bytes.
  • Never merge conflicts: write a conflicted copy and let the human decide. Silent last-writer-wins loses someone's work.
hard part

Sync correctness under offline edits, renames, and case-insensitive filesystems: the state machine of "what changed where" is the product.

YouTube

testing: transcoding pipeline, CDN economics

components

upload service, raw store, DAG transcoding pipeline over GOP-aligned segments, HLS/DASH packaging, CDN, metadata DB, view-count stream

key decisions
  • Split into segments, transcode in parallel across the resolution and codec ladder: hours of serial work becomes minutes.
  • Adaptive bitrate (HLS/DASH): the client switches rungs per network; you ship a ladder, not a file.
  • Zipfian popularity: CDN the head, keep the endless tail on cheap origin storage; view counts are approximate live, exact in batch.
hard part

Cost. Transcode compute and CDN egress dominate everything; per-title encoding settings and tiered storage are where the money is.

Live streaming (Twitch)

testing: latency tiers vs viewer scale

components

ingest (RTMP/SRT), realtime transcode ladder, packager, CDN delivery (HLS or LL-HLS), chat fanout, VOD archiver

key decisions
  • Protocol picks your latency tier: plain HLS (5 to 30 s) rides any CDN; LL-HLS lands around 2 to 5 s; WebRTC goes sub-second but needs stateful media servers and loses the CDN.
  • Transcode must keep up in real time: dedicated GPU pool, and degrade by dropping renditions, never the stream.
  • Chat is its own system: sharded pub/sub per channel with slow-mode rate limiting.
hard part

Start storms: a raid drops 100k viewers on a stream in seconds, so edge caches must fill from one origin fetch, not 100k.

Proximity service (Yelp, nearby friends)

testing: geo indexing tradeoffs

components

business write service (rare writes), location search service (heavy reads), geo index, cache, ranking

key decisions
  • Geohash vs quadtree: geohash is prefix search on an ordinary B-tree or Redis, fixed grid, easy to shard; a quadtree adapts to density but lives in memory and needs rebuilds. Geohash in a DB is the pragmatic answer.
  • Search the cell plus its 8 neighbors: two points can sit adjacent across a cell boundary, so one cell is never enough.
  • Nearby friends flips the physics: live locations mean websockets and pub/sub on updates, not a static index.
hard part

Precision choice under wild density variance: the right cell size for Manhattan is wrong for Montana.

Google Maps

testing: precomputation over graphs, tile serving

components

tile pipeline and CDN, geocoder, road graph, routing service, live traffic ingestion, ETA model, navigation sessions

key decisions
  • Tiles are precomputed and immutable at about 21 zoom levels, CDN-cached; vector tiles let the client restyle without new bytes.
  • Plain Dijkstra does not scale to a continent: contraction hierarchies (precomputed shortcuts over important nodes) make cross-country routes take milliseconds.
  • Traffic blends into edge weights from live probe speeds; reroute active sessions when the delta is significant.
hard part

Freshness of the precomputed routing structures: traffic changes the weights that the shortcuts were built on.

04Marketplaces and money

Where retries meet money

Money changes the physics. Delivery is at-least-once, effects must be exactly-once, and every answer in this section eventually becomes idempotency keys, ledgers, and reconciliation.

Uber

testing: live location at scale, dispatch consistency

components

rider and driver apps, location ingestion, in-memory geo index of drivers (geohash/S2 cells), matching service, trip state machine, surge pricing, ETA service

key decisions
  • Driver pings are ephemeral: update an in-memory cell-to-drivers index every few seconds, append the stream to Kafka for analytics, and keep durable writes off the hot path.
  • Match by ETA, not straight-line distance: the river in between matters.
  • Lock the driver with a short lease during the dispatch offer, so two riders cannot claim one car.
hard part

Exactly-one-driver-per-request without a global lock, and surge computed per cell from sliding-window supply and demand counters.

Food delivery (DoorDash)

testing: three-sided marketplace, state machines

components

order state machine, restaurant and menu service with caches, courier dispatch (geo matching), prep-time and ETA prediction, notifications, payments

key decisions
  • The order is a state machine on an event log (created, paid, accepted, cooking, picked up, delivered): three apps are three views of one machine.
  • Dispatch is timed to prep prediction: the courier should arrive as the food does, and batching orders trades their time against your margin.
  • Menu and hours caches need tight TTLs: a stale menu is a cooked refund.
hard part

ETA composition (prep plus travel plus parking plus handoff), each term with its own error bars, promised to the customer as one number.

Payment system

testing: correctness under retries, reconciliation

components

payment API, risk checks, PSP integration, double-entry ledger, wallet service, webhook handler, nightly reconciliation

key decisions
  • Idempotency key on every mutation: the server stores key to result and replays it, so a client retry can never double-charge.
  • Double-entry, append-only ledger: every movement is two entries summing to zero, balances are a fold over the log, nothing is updated in place.
  • No distributed transaction with the PSP: a local state machine advanced by webhooks and polling, then reconciliation against the PSP's records to catch drift.
hard part

Exactly-once effect built entirely out of at-least-once parts: retries, idempotency, and reconciliation are the whole design.

Digital wallet

testing: cross-shard transfer atomicity, hot accounts

components

API, sharded balance partitions, transfer coordinator, event-sourced ledger, CQRS read views

key decisions
  • Cross-shard transfer: 2PC is atomic but blocks on coordinator failure; sagas trade atomicity for availability via compensating transactions; event sourcing with deterministic replay makes every balance reproducible and auditable.
  • Balances are views over the event log, rebuildable from history: the audit trail is the data model.
  • Hot accounts (a merchant credited thousands of times a second): shard into sub-accounts and merge periodically.
hard part

Atomic transfer across shards at high throughput, and proving to an auditor that the books balance at any past instant.

Stock exchange

testing: determinism, microsecond tails, fairness

components

gateway with risk checks, sequencer, in-memory matching engine (order book per symbol, price-time priority), market data publisher, execution reports, replicated event log

key decisions
  • Single-threaded matching engine per symbol: no locks, deterministic, microseconds. Scale across symbols, never inside one book.
  • A sequencer totally orders all events; replicas replay the same sequence into the same state: failover is state machine replication, not backup restore.
  • Fairness is architecture: price-time priority in the book, market data fanned out without favorites.
hard part

Failover that provably loses and reorders nothing, and p99.9 in microseconds, which means no GC and no syscalls on the hot path.

Ticketmaster

testing: contention on scarce inventory

components

event and seat inventory DB, virtual waiting room, reservation service with TTL seat holds, payment, confirmation

key decisions
  • Seat hold is a conditional update: UPDATE ... WHERE status = 'free' with a reserved-until expiry; optimistic beats pessimistic locking when most claims fail anyway.
  • The waiting room is the design: admit users in batches so 10 million people compete at a concurrency the database survives.
  • Holds expire server-side via a sweep or delay queue; the client timer is a lie you rendered.
hard part

Not overselling seat 14B under thousands of simultaneous claims while the site stays up for browsing.

Hotel reservation

testing: inventory modeling, overbooking as policy

components

inventory service (rooms per type per date), search, reservation service, payment, admin tooling

key decisions
  • Book room types, not rooms: inventory is a counter per (hotel, type, date); the physical room is assigned at check-in. This collapses the whole problem.
  • Concurrency by conditional decrement (WHERE available >= 1) or a version column; a multi-night stay must decrement every date atomically, all or none.
  • Overbooking is a business rule, say 110%, sitting inside the same conditional: cancellations are expected inventory.
hard part

Multi-night atomicity across date rows, and keeping cached availability honest enough that search rarely sells what booking cannot deliver.

Ad click event aggregation

testing: streaming correctness at billing grade

components

click stream (Kafka), stream aggregator (Flink), minute windows keyed by ad id, watermarks, aggregate store, batch recompute from the raw log

key decisions
  • Stream for live numbers, batch recompute from the immutable raw log for the invoice: clicks are money, so keep a path to the exact answer.
  • Event time with watermarks, never processing time: late clicks land in the right minute or emit a correction record.
  • Exactly-once aggregation via checkpointing plus a transactional or idempotent sink.
hard part

Late and duplicate events at billing accuracy, with fraud filtering upstream deciding which clicks were ever real.

05Infra and realtime

The plumbing, plus the AI-era two

What runs underneath the products, and the two questions YC startups actually ask now: the LLM gateway and the RAG pipeline. Both connect straight into the flagship below.

Metrics monitoring and alerting

testing: time-series write patterns, cardinality

components

agents or scrape targets, ingestion buffer (Kafka), time-series DB, downsampling rollups, query service, alert evaluator, notification routes

key decisions
  • A real TSDB: Gorilla-style delta-of-delta timestamps and XOR-compressed floats get a point near 1.4 bytes, with the recent window in memory because most queries are "last hour".
  • Pull vs push: scraping (Prometheus) gives discovery and up/down for free; push suits ephemeral jobs behind NAT.
  • Retention tiers: raw for weeks, rollups for years; alerts evaluate over the stream with a for-duration to kill flapping.
hard part

Cardinality explosions: one user-id label mints millions of series and the index, not the data, kills you.

Real-time leaderboard

testing: knowing when a sorted set is the whole answer

components

score ingestion, Redis sorted set, durable score log behind it, monthly key rotation, client fanout

key decisions
  • Redis ZSET is the design: ZINCRBY to score, ZREVRANK for rank, O(log n) each; tens of millions of players fit one node's memory. SQL ORDER BY cannot compete.
  • Redis is a view, not the truth: the score events live in a log or DB, so a lost node is a replay, not a loss.
  • Past one node: shard by score range (rank = local rank + counts above) or scatter-gather top-k across user shards.
hard part

Exact global rank for a mid-pack player across shards, and tie-breaking rules that stay stable under concurrent updates.

Collaborative editor (Google Docs)

testing: convergence, OT vs CRDT

components

client editors, websocket sync service, per-document session with an ordered op log, transform or merge engine, snapshots plus compaction, presence and cursors

key decisions
  • Operational transform vs CRDT: OT transforms concurrent ops against each other and needs a central sequencer (Google Docs); CRDTs commute by construction and work offline and peer-to-peer at a metadata cost (Yjs, Figma-adjacent). With a central server, OT stays simpler.
  • Document = op log + periodic snapshots: a joining client loads the snapshot and replays the tail.
  • Sticky per-document sessions: every op for a doc flows through one node for ordering; shard by document id.
hard part

Proving convergence for every concurrent op pair in OT, or taming CRDT tombstone and metadata growth over a document's lifetime.

Email service (Gmail)

testing: storage model, private search, deliverability

components

SMTP ingress with spam and virus pipeline, immutable message store, label and thread metadata, per-user search index, IMAP/HTTP API, outbound MTA

key decisions
  • Messages are immutable: folders and labels are metadata pointers, so "move to archive" is a metadata write; attachments dedup by content hash.
  • Search index per user, built at delivery time: privacy isolation and a small index beat one global index for private corpora.
  • Outbound is reputation management: IP warm-up, SPF/DKIM/DMARC, bounce processing. Deliverability, not code, is the moat.
hard part

Spam classification at ingress, and IMAP flag sync semantics across devices that disagree about state.

Video conferencing (Zoom)

testing: realtime media topology

components

signaling service (websocket), SFU media servers, STUN/TURN, simulcast layers, regional media routing, recording pipeline

key decisions
  • SFU over MCU over mesh: mesh uploads O(n) streams per client; an MCU decodes and mixes (CPU-heavy, adds latency); an SFU just forwards selected streams. SFU is the standard for 3+ people.
  • Simulcast: each client uploads 2 or 3 quality layers and the SFU picks per receiver, so no transcoding sits on the hot path.
  • UDP/RTP with jitter buffers, FEC and loss concealment: retransmission arrives too late for live audio; latency beats completeness.
hard part

Bandwidth estimation and mid-call adaptation without visible quality thrash; cascade SFUs across regions for big meetings.

LLM inference gateway

testing: streaming, token economics, GPU-era capacity

components

API gateway with SSE streaming, model router, token budgeter, priority queue, prompt caches, provider adapters with circuit breakers, usage metering

key decisions
  • Rate limit in tokens, not requests: one request can cost 100x another. Admit on an estimate, settle on actuals against per-key budgets.
  • Stream end to end: time to first token is the UX metric, so SSE passthrough with no buffering middlebox.
  • Cache in layers: exact-match for repeated prompts, prefix/KV-cache reuse for shared system prompts, semantic caching only with an honest staleness story.
  • Route cheap-first with escalation, health-based failover across providers, and retry only if no token has been streamed yet.
hard part

Fairness under GPU scarcity: multi-second requests mean queueing theory and per-tenant isolation, not just a limiter.

RAG pipeline

testing: retrieval quality as a systems problem

components

ingest and chunker, embedding workers, vector index (HNSW) plus keyword index (BM25), hybrid retriever, cross-encoder reranker, prompt assembly with citations, eval harness

key decisions
  • Chunk by document structure (headings, paragraphs) with overlap, a few hundred tokens each: chunk boundaries decide answer quality more than the model does.
  • Hybrid retrieval merged by reciprocal rank fusion: embeddings miss exact identifiers, keywords miss paraphrase; you need both.
  • Retrieve wide, rerank narrow: cheap recall to top 50, cross-encoder precision to top 5, and carry source ids through so every claim cites a chunk.
hard part

Freshness and eval: upsert with tombstones on doc change, a full reindex on embedding-model upgrade (version your embeddings), and continuous retrieval metrics or quality silently rots.

06The flagship

VoicerAI: the one I own

Read all, drill eight, own one: this is the one. I built voicer-ai, a real-time voice agent, and this is how I present it as a system design. The full build story, live demo included, is at anishfyi.com/voicer/.

mark the flagship read

requirementsWhat I set out to build

A real-time voice conversation with a machine: sub-second perceived response, the ability to interrupt it mid-sentence and have it stop (barge-in), zero per-minute cost, and nothing leaving the machine. No API keys, no cloud bill, every stage a free local model. About 750 lines of Python, no framework, and every stage swappable between a local and a cloud provider behind one interface. The non-functional requirement that dominates everything else is latency: humans read anything past 1.5 seconds of silence as broken.

architectureThe cascade

mic30 ms frames
VADsilero / energy
ASRfaster-whisper
LLMollama, streamed
chunkersentence bounds
TTSpiper
speakerinterruptible
the VAD keeps running during playback. that single loop is barge-in

I chose a cascaded pipeline over a speech-to-speech model deliberately. The cascade gives me text in the middle: every stage is inspectable, every provider is swappable, and the transcript is a free audit trail for logging, evals, and deterministic tool calls. Speech-to-speech models collapse the stages for latency and keep prosody, but tools, evals and audits all get harder. For an agent that does things, I want the text seam. For a companion where naturalness is the product, I would choose the other way, and I can argue both sides.

latencyThe budget, term by term

StageLocal modelsCloud APIsWho controls it
VAD endpoint (hangover)300-700 ms300-700 msyou. a constant you chose
ASR (5 s utterance)300-800 ms200-500 msmodel + streaming
LLM to first sentencen/a300-700 msmodel + prompt length
TTS first audio buffer100-400 ms150-400 msprovider + streaming
Total to first audio0.9-2.6 s0.95-2.3 s

Under 300 ms feels magical, 500 to 800 ms feels fine, past 1.5 s the agent reads as slow, past 3 s people repeat themselves or hang up. The number that should bother you: the largest single line item is the hangover, the 600 ms of silence (20 frames at 30 ms) I wait before declaring the turn over, and it is a constant I chose. No model upgrade touches it. That is why the frontier is semantic endpointing and turn prediction, not better silence detection. The back-of-envelope habit is the same one as the estimation drill: know your budget before you defend your boxes.

controlThe state machine, and the one insight

LISTENING --speech end--> THINKING --first sentence--> SPEAKING
    ^                                                      |
    +--------- barge-in, or all sentences spoken ----------+

Three states, one loop, and the thing that is not in the diagram is the whole trick: the mic thread and the VAD never stop, even while the agent is speaking. Barge-in is not a feature bolted on top; it is just the VAD staying on during playback, pushing an event when it hears you, and the main loop stopping the player. If the mic muted during SPEAKING, the transition out of it could never fire. On interruption there are four decisions: stop playback immediately, discard the unspoken sentences, record in history only what was actually said aloud (or the model later references things you never heard), and treat any in-flight tool call as the dangerous case: finish, cancel, or make it idempotent.

speedSentence-level pipelining

# the trick that makes it feel alive
without:  TTFA = ASR + full LLM response + TTS(all of it)
with:     TTFA = ASR + LLM first sentence + TTS(first sentence)

I chunk the LLM's token stream into sentences and hand sentence one to TTS while the model is still writing sentence three. For a three-sentence answer that is roughly a 3x cut in perceived latency, with no better model, no faster hardware, no spend. The catch is boundary detection in a live stream: "Dr." looks like the end of a sentence, and my chunker once stalled on exactly that, silently reverting the whole system to full-response latency while every test stayed green. A latency bug that produces correct output is invisible to a correctness suite.

failureHow it dies in practice

  • False endpointing on thinking pauses. Hangover too short and the agent cuts you off mid-thought, then cuts off your correction too. Trust dies in one turn. Too long and every turn pays dead air.
  • ASR fed silence hallucinates. Endpointing mistakes hand Whisper near-silent audio and it invents text; faster-whisper's own VAD filter runs as a second line of defense on exactly this.
  • TTS latency spikes hide between sentences. Playback blocks the loop, so a slow synthesis call shows up as a mid-reply gap, not as TTFA. If you only measure TTFA, you will not see it.
  • Echo makes the agent interrupt itself. Without acoustic echo cancellation the mic hears the speaker, VAD fires, and the agent stops for its own voice. The repo assumes headphones and says so; production means AEC.

scaleFrom one laptop to a fleet

The single-process design maps cleanly onto a service. Websocket ingress with session affinity: a conversation is stateful (VAD state, dialogue history, playback position), so a session pins to one stateful session worker that owns its state machine. The real decision is model serving: a per-session monolith (all stages colocated with the session) is simple and keeps hops off the critical path but wastes accelerators, versus shared per-stage pools: an ASR pool, an LLM pool with continuous batching, a TTS pool. Pools win on utilization and independent scaling, and cost you network hops inside the latency budget. My answer: colocate at small scale, then pool the LLM first, because it is the most expensive stage and batches best. Observability is per-stage latency histograms, p50 and p99, with TTFA measured from the user's last word, not the endpoint decision: the second zero flatters you by the entire hangover. Then barge-in success rate and false barge-in rate, because those are the product.

pitchSixty seconds, cold

voicer-ai is a real-time voice agent I wrote to own the whole stack instead of one API call: mic into a voice activity detector, then speech recognition, then a streaming language model, then text to speech. About 750 lines of Python, no framework, every stage swappable between a local model and a cloud API, total running cost zero. Two pieces make it feel alive. Barge-in: the mic thread never stops, so when you interrupt, it stops. And sentence pipelining: I chunk the token stream into sentences and synthesize the first while the model writes the third, which turns time to first audio into recognition plus one sentence instead of recognition plus the whole response. The honest limitation: utterance-level recognition, not streaming, and no echo cancellation, so barge-in wants headphones. Both are written down in the repo, because naming your own gaps is cheaper than being caught by them.

github.com/anishfyi/voicer-ai ยท the deep dive: anishfyi.com/voicer/