Design a rate limiter, URL shortener or KV store

15 system design interview questions on rate limiters, URL shorteners, caches and key value stores, each with the correct answer and a full explanation.

A rate limiter for a 50k QPS API on 40 nodes: limits must be global per user, short bursts allowed. Which design?
Show answer

Answer

Token bucket per user in Redis, updated atomically with a Lua script
A shared token bucket enforces one global limit and allows bursts up to bucket size; Lua keeps refill and take atomic. Per node counters let a user get 40x the limit by spreading requests. Fixed windows allow up to 2x the limit at a window edge. A SQL insert per request at 50k QPS is too slow and grows forever.

Source: Scaling your API with rate limiters, System Design Interview: An Insider's Guide, Volume 1

A fixed window limiter allows 100 requests per minute, windows starting on the minute. What is the most a client can send in 2 seconds?
Show answer

Answer

200
Send 100 at 0:59 and 100 at 1:00; each window sees exactly 100. Sliding window or token bucket fixes this. 100 assumes windows slide. 101 and 150 have no basis in the window math.

Source: System Design Interview: An Insider's Guide, Volume 1

Clients exceed the limit under concurrency. What is wrong?
count = int(redis.get(key) or 0)
if count < LIMIT:
    redis.set(key, count + 1, ex=60)
    allow()
Show answer

Answer

GET and SET are separate, so concurrent requests read the same count; use INCR or a Lua script
Single threaded means each command is atomic, not a sequence of commands. Two requests can both GET 5 and both SET 6. INCR returns the new value atomically, with EXPIRE on the first increment, or do it in Lua. This code also resets the TTL on every SET. A longer TTL does not fix the race. A local cache makes counts less accurate.

Source: Redis: INCR, pattern: rate limiter

Design a distributed rate limiter for a public API. Give requirements, the algorithm and two deep dives.
Show answer

Answer

  • Requirements: per user or key limits, global across nodes, low added latency (under a few ms), fail open or closed decided up front, clear 429 with Retry-After.
  • Algorithm: token bucket (bursts) or sliding window counter (smooth), state in Redis keyed by client, updated in a Lua script.
  • Placement: API gateway or middleware before business logic.
  • Deep dive 1, scale: shard Redis by key, local pre-check or batching for hot keys, accept slight overrun.
  • Deep dive 2, failure: Redis down means fail open for most APIs (availability) or closed for abuse sensitive ones, with local fallback limits.

Mention multi region: per region limits or async sync of counters with tolerance.

Source: System Design Interview: An Insider's Guide, Volume 1, Scaling your API with rate limiters

A URL shortener creates billions of codes on many writer nodes. Codes must never collide and should be short. How do you generate them?
Show answer

Answer

Hand each node unique counter ranges (or Snowflake style IDs) and base62 encode
Disjoint ranges or Snowflake IDs are unique without coordination per request, and base62 keeps them short. Truncated hashes collide and need a check and retry. A single auto increment database is a bottleneck and single point of failure. A UUID is 36 characters, defeating the short URL.

Source: System Design Interview: An Insider's Guide, Volume 1, Announcing Snowflake

Your shortener must record every click for analytics. Which redirect status do you return?
Show answer

Answer

302 Found
302 is temporary, so browsers come back to you on every click. 301 and 308 are permanent and cacheable by default, so repeat clicks skip your server and are not counted. A 200 with meta refresh is slower, worse for clients and crawlers, and not a redirect.

Source: RFC 9110: HTTP Semantics, System Design Interview: An Insider's Guide, Volume 1

You need room for 100 billion short codes using base62 characters. What is the minimum code length?
Show answer

Answer

7
626 is about 57 billion, too few. 627 is about 3.5 trillion, enough. 8 and 10 work but are longer than needed.

Source: System Design Interview: An Insider's Guide, Volume 1

Design a URL shortener like bit.ly. Requirements, API, data model and the two deep dives.
Show answer

Answer

  • Requirements: create short URL, redirect, optional custom alias and expiry. Read heavy (100:1), redirect p99 low.
  • API: POST /urls {longUrl} returns code; GET /{code} returns 302.
  • Data: code to long URL, owner, created, expiry. Key value store or sharded SQL keyed by code.
  • Deep dive 1, IDs: counter ranges or Snowflake, base62, 7 characters.
  • Deep dive 2, reads: cache code lookups (cache aside, LRU), CDN or edge caching, async click logging through a queue.

Source: System Design Interview: An Insider's Guide, Volume 1, The System Design Primer, Hello Interview: Amazon L6 interview guide

Each cache node needs O(1) get, O(1) put and LRU eviction. Which structure?
Show answer

Answer

Hash map plus doubly linked list
The map finds the node, the list moves it to the front and pops the tail, all O(1), which is what OrderedDict gives you. A heap needs O(log n) per access update. A sorted array needs O(n) moves. Scanning on eviction is O(n).

Source: Python documentation: collections.OrderedDict

A single node LSM key value store writes to its WAL with fsync, updates the memtable and acks. It crashes before flushing the memtable. Why is no acked data lost?
Show answer

Answer

The WAL is replayed on restart to rebuild the memtable
The write ahead log is the durable copy until the memtable flushes to an SSTable. The memtable is in memory. The question says single node, so there are no replicas. SSTables are written on flush, not per put, which is what makes LSM writes fast.

Source: The Log-Structured Merge-Tree (LSM-Tree), Bigtable: A Distributed Storage System for Structured Data

An LSM store keeps a Bloom filter per SSTable. What does it do on a read?
Show answer

Answer

Skips SSTables that definitely do not contain the key
A Bloom filter has no false negatives and some false positives, so it can rule out SSTables and save disk reads, but a yes is only a maybe. It cannot point to the one table that has the key. Compression and sort order are separate parts of the SSTable format.

Source: Space/Time Trade-offs in Hash Coding with Allowable Errors, Bigtable: A Distributed Storage System for Structured Data

A shopping cart store must accept writes during a network partition, and an item added on either side must never be lost. How do you resolve concurrent versions?
Show answer

Answer

Track versions with vector clocks and merge the siblings (union of items)
Vector clocks detect concurrent versions and the application merges them, which is Dynamo's cart design. Last write wins discards one side's item. Rejecting writes or requiring a leader breaks the always writable requirement.

Source: Dynamo: Amazon's Highly Available Key-value Store

Design a distributed key value store. Requirements, partitioning, replication and the write and read paths.
Show answer

Answer

  • Requirements: get and put, large data, tunable consistency, high availability.
  • Partitioning: consistent hashing with virtual nodes.
  • Replication: N replicas on the next distinct nodes; quorum R and W with R + W greater than N for strong reads.
  • Write path: WAL, memtable, flush to SSTables, compaction.
  • Read path: memtable, then SSTables with Bloom filters.
  • Failures: hinted handoff, read repair, anti entropy with Merkle trees, gossip membership.
  • Conflicts: vector clocks or last write wins, chosen per use case.

Source: System Design Interview: An Insider's Guide, Volume 1, Dynamo: Amazon's Highly Available Key-value Store, The Log-Structured Merge-Tree (LSM-Tree)

Why do Twitter style Snowflake IDs fit tweet IDs better than UUIDv4?
Show answer

Answer

They are 64 bit and roughly time sortable, generated without coordination
Snowflake packs a timestamp, a worker id and a sequence into 64 bits, so IDs sort by time and fit in a long index column. They are only roughly ordered across machines, not strictly sequential. They depend heavily on the clock. Encoding is not what makes them short, the 64 bit size is.

Source: Announcing Snowflake

An NTP adjustment moves a Snowflake worker's clock back 50 ms. What should the generator do?
Show answer

Answer

Refuse to generate (or wait) until the clock passes the last timestamp used
Generating with an earlier timestamp can reproduce IDs already issued in that millisecond, so the generator must wait or error. Keeping on or resetting the sequence risks exact duplicates. Going further back makes it worse.

Source: Announcing Snowflake

These questions come from the System Design Interview Practice pack in Bertopia.

More system design topics

Practise these on your commute