Skip to content
Backend & systems

29 System Design Interview Questions and Answers

This focused guide turns RecallDeck’s curated System Design material into 29 interview-ready questions. Answer each one before opening the explanation, then use the examples and edge cases to repair anything vague or incomplete.

31 min read29 detailed answersUpdated
What to remember

Organize the answer around ownership, limits, failure, and recovery. Definitions become interview-ready when they survive a concrete production scenario.

Question set

29 detailed answers

A recorded source review is available for 29 of 29 answers. Each reviewed answer lists its date and scope.

01

Step 1. Clarify the requirements

Short answer: Before designing, clarify features, users, and scope, then load, latency, availability, consistency, and durability. State your assumptions explicitly: requirements determine which trade-offs are acceptable.

Never start designing right away. First clarify exactly what you're building. Split the requirements into two groups.

Functional requirements — what the system does (features):

  • What are the main scenarios? (e.g.: "shorten a link" and "follow a short link").
  • Who are the users? How many? Geographic distribution?
  • What's NOT in scope? (analytics, authentication, billing — often can be dropped, as long as you say so explicitly).

Non-functional requirements (NFRs) — what properties it has:

  • Scale: how many users / requests / data.
  • Availability: which operations must succeed, within what latency, and what failure rate is acceptable? During a network partition, specify consistency/availability trade-offs per operation.
  • Latency: hard requirements (p99 < 100 ms) or not.
  • Consistency: is eventual consistency acceptable, or do you need strong consistency.
  • Durability: can data be lost (logs vs payments).
  • Read/Write ratio: is reading or writing dominant.

💡 Ask questions out loud and record the answers — that alone is half the evaluation. Example: "Do we need click analytics? If so, that changes the data model. I'll assume only a basic counter is in scope."

Sources: Reference 1, Reference 2.

02

Step 2. Estimate the scale (back-of-the-envelope)

Short answer: Estimate average and peak QPS, the read/write mix, record size, bandwidth, and retention. Check units and explain which design decision each estimate informs; derive peaks from measurements or explicit assumptions.

Rough estimates that will shape the architecture. Calculate out loud and round aggressively.

What to estimate:

  • QPS (queries per second): average and peak (use measured peaks; 2–3× average is only an explicit example assumption).
  • Data volume: size of one record × number of records × retention horizon.
  • Read/write ratio (read:write) — determines whether you need read replicas and a cache.
  • Bandwidth: QPS × response size.
  • Storage: growth per year.

Handy numbers for mental math:

Quantity Value
Seconds in a day ~86,400 ≈ 10⁵
Month ~2.5M seconds
1M requests/day ≈ 12 QPS on average
1B requests/day ≈ 12,000 QPS

Example calculation (URL shortener): 100M new links/month → 100M / 2.5M sec ≈ 40 writes/sec. Read:write = 100:1 → 4000 reads/sec. Record size ~500 bytes → 100M × 500B = 50 GB/month → 600 GB/year → ~6 TB over 10 years.

💡 State the conclusion from the numbers right away: "These estimates suggest starting with one database, then testing the actual queries and latency target before adding a cache or replicas."

Sources: Reference 1.

03

Step 3. Define the API

Short answer: Define API operations, inputs, responses, errors, authorization, pagination, and retry behavior. For idempotency, bind the key to the caller and payload, and commit its result atomically with the business effect.

Describe the service contract — this pins down the functionality and helps the interviewer understand the model. REST/gRPC, the main endpoints:

POST /api/v1/urls           {long_url, custom_alias?, ttl?} -> {short_url}
GET  /{short_key}           -> 301/302 redirect

Talk through: methods, parameters, idempotency (is POST idempotent?), pagination for lists (cursor-based, not offset on large data sets), authorization (api_key / token).

The contract must cover more than the happy path: error codes, size limits, timeouts, and retry behavior. For resource creation the client can send an Idempotency-Key; the server stores the key with the result and returns the same response on a retry. This makes the API a testable system boundary rather than a list of accidental URLs.

request → validate/auth → domain operation → stable response
              │                 │
          4xx contract      retry-safe effect

Bind an idempotency key to the caller and request payload, and commit its result with the business effect. Two simultaneous retries must not both pass a separate key check. Expiry defines the retry window you support.

Sources: Reference 1.

04

Step 4. Data model and database choice

Short answer: Choose the data model and database from access patterns and invariants: entities, keys, relationships, and atomic changes. Check the specific database and read-mode guarantees; SQL or NoSQL alone does not determine consistency.

Start from access patterns and invariants. List entities, keys, relationships, frequent queries and atomic changes before selecting a database.

  • A relational database is useful for joins, constraints and multi-row transactions. Choose its isolation level and replication/read policy; the label SQL alone does not promise every read is current.
  • Key-value and wide-column stores suit known key-based queries and partitioned workloads. Their consistency guarantees depend on the product and operation: DynamoDB has strongly consistent reads for supported resources, while global secondary indexes use eventual reads.
  • A document model is useful when nested data is usually read/updated together. Flexible schema still requires validation and migrations.
  • Redis can support caches, counters and transient coordination; decide persistence and recovery requirements before making it authoritative.

For a URL shortener, begin with key → destination, owner, expiry and a unique-key constraint. Choose sharding only when capacity, isolation or geography justify it; name the partition key and explain hot-key and cross-partition behavior.

Sources: Reference 1.

05

Step 5. High-level architecture

Short answer: Show the minimum request path through the necessary components and identify the source of truth. Describe protocols, timeouts, and synchronous versus asynchronous calls, plus state ownership and recovery. Walk through a successful request and a failure.

Describe in words (or with a diagram) the components and the request flow:

[Client] -- DNS lookup resolves address; HTTP request --> [Load Balancer] -> [API / App Servers (stateless)]
                                              |-> [Cache (Redis)]
                                              |-> [Database (+ read replicas)]
                                              |-> [Message Queue] -> [Workers]
                                         [CDN] -> [Blob Storage (S3)]

Trace a typical request along this path from the client all the way to the DB and back.

Do not draw every component you know. Start with the minimum path that satisfies the requirements, then add a cache, queue, or replica only when the estimates justify it. For every edge state the protocol, sync/async behavior, timeout, and source of truth; for every stateful component state ownership, replication, and recovery.

Walk both a normal request and a failure. For example, a cache miss reads the database and fills the cache; if the database is unavailable, the request stops at a bounded timeout, a circuit breaker limits the cascade, and metrics expose the degraded path. A component diagram only becomes an architecture when its flows and failures are explained.

Sources: Reference 1, Reference 2.

06

Step 6. Drilling into the key components

Short answer: Choose one or two key components and explain requirements, invariants, the algorithm, state, and failure recovery. Use a concrete request to show how the design preserves its guarantees and what that costs.

Go deep on the 1-2 most interesting parts (the ones that make the problem distinctive): key generation, the feed ranking algorithm, message delivery, the caching scheme. This is where the main depth lives.

For the selected part, define the data and invariants, walk the algorithm on one concrete request, estimate complexity, and analyze at least one failure. For ticket reservation, for example, the invariant is sold + held ≤ capacity; show the atomic conditional update, hold TTL, and idempotent payment retry rather than merely drawing service boxes.

requirement → invariant → algorithm → state → failure → recovery

Finish with a measurable trade-off: what the design improves, what it costs, and which workload change would force you to revisit it.

07

Step 7. Bottlenecks, scaling, fault tolerance, trade-offs

Short answer: Identify bottlenecks and single points of failure, then explain scaling and recovery for critical layers. Finish with explicit trade-offs: what improves, what it costs, and which workload changes would make you revisit the design.

  • Where is the bottleneck? (usually the DB on reads/writes, or a single component).
  • How to scale each layer (horizontal scaling is preferred).
  • Where is the single point of failure? How to make it redundant.
  • Which trade-offs you accepted and why (consistency vs availability, cost vs latency).

💡 A good answer always ends with something like: "Here I chose X, sacrificing Y, because for this scenario Z matters more. The alternative is W, which is better under a different load profile."

08

Load Balancer and Reverse Proxy

Short answer: A load balancer distributes traffic; a reverse proxy forwards requests on behalf of servers, and one component can do both. L4 uses transport information; L7 understands HTTP. Design routing, health checks, connection draining, and redundancy for the workload.

A load balancer distributes traffic; a reverse proxy forwards requests on behalf of servers. One component often does both.

An L4 balancer routes connections using transport information. An L7 proxy understands HTTP and can route by path or headers, terminate TLS and apply application policies. L4 is not universally faster: connection reuse, TLS work, implementation and workload determine the measured result.

Select a policy such as round-robin or least-connections, define health checks, and drain old connections during deployment. Multiple application instances do not help if the balancer itself is a single point of failure.

Keep durable session state outside one application process, using an appropriate shared store or a securely designed token scheme. Redis is one option, not a requirement. Long-lived WebSocket connections remain attached to a gateway until reconnect; reconnection and state recovery must be designed explicitly.

Sources: Reference 1, Reference 2.

09

Caching

Short answer: A cache stores a reusable copy to reduce latency or source load. Define the source of truth, acceptable staleness, and update or invalidation rules; TTL alone does not eliminate races. Plan concurrent fills and recovery separately.

Caching keeps a reusable copy near readers to reduce latency or source load. Identify the source of truth and how stale a result may be.

  • Cache-aside: read the cache; on a miss read the database and populate the cache. After a write, invalidate or update the affected key.
  • Write-through: route writes through a layer that updates the backing store and cache. It adds work to the write path; atomicity, failure handling and concurrent reads still determine consistency.
  • Write-back: acknowledge a cached write before asynchronously persisting it. This needs durable buffering and recovery if acknowledged data must survive failure.

TTL bounds a cache entry's lifetime, not every possible stale-data race. For example, a reader can fetch an old database value, a writer can invalidate the key, and the reader can then fill it with the old value. Use suitable version checks, ordered updates or bypass the cache for strict invariants.

For stampedes, coalesce concurrent fills per key and use bounded waits, TTL jitter or early refresh. Measure hit rate and tail latency; a cache is not automatically faster for every workload.

Sources: Reference 1, Reference 2.

10

CDN

Short answer: A CDN serves cacheable content closer to users and reduces origin load. Define cache keys, freshness, and updates; do not collapse personalized responses into a shared cache. Measure hits and misses separately.

  • Caches static assets (images, video, JS/CSS) closer to the user (edge). Reduces latency and load on the origin.
  • Use it for any heavy static / rarely changing content and for a geographically distributed audience.

The cache key consists of the URL plus selected request headers. Content-hashed filenames support a long freshness lifetime such as Cache-Control: public, max-age=31536000, immutable; mutable HTML needs a short TTL or an explicit purge. Dynamic responses are cacheable only when they are public and the key includes every variant—never let personalized cookies collapse into one shared response.

client ─► nearest edge ──HIT──► response
                │
               MISS ─► origin ─► cache at edge

⚠️ A cache miss still requires origin work; routing and connection reuse may change latency, so measure hits and misses separately. Protect the origin from stampedes and track hit ratio separately by content type.

immutable tells caches that a fresh response will not change; it does not set the freshness lifetime by itself. Documentation.

Sources: Reference 1, Reference 2.

11

Databases: replication, sharding, partitioning

Short answer: Replication copies data for reads and recovery; partitioning divides a dataset, and sharding distributes partitions across nodes. The key affects hot partitions and cross-node work; replica guarantees depend on the protocol and read policy.

  • Replication (read replicas): copies of database data can support read scaling and recovery. In a primary/async-replica design, writes go to the primary and replica reads can lag; route reads by their freshness requirements.
  • Sharding: horizontally splitting data across multiple DBs by a key. Scales writes and volume. Strategies: by key hash, by range, by geo. Downsides: complex JOINs/transactions across shards, rebalancing, hot shards.
  • Partitioning — dividing a dataset into pieces, for example by date range or list. A database can partition a table internally; sharding distributes data partitions across nodes.
  • Choosing the shard key is critical: it must distribute evenly and match the access pattern.

The replica discussion describes a common primary/async-replica setup, not every database. Synchronous replication and read rules change its guarantees. Partitioning is the general division of data; sharding usually means distributing those partitions across independently hosted database nodes.

Sources: Reference 1.

12

Queues and asynchronous processing

Short answer: A queue separates accepting work from processing it and buffers bursts, but does not remove load. Define delivery, idempotency, retries, a dead-letter queue, and backpressure; an atomic local database change does not automatically make an external effect exactly once.

  • A Message Queue (Kafka, RabbitMQ, SQS) decouples producer and consumer. Use it for: heavy tasks outside the user's request (sending email, video processing, feed fan-out), smoothing out load spikes (a buffer), and retries on failures.
  • Delivery guarantees: at-most-once / at-least-once / exactly-once. Most often at-least-once + idempotent consumers.
  • Publish through a transactional outbox; make consumers deduplicate a stable message_id in the same transaction as the business effect. Send transient failures to a retry topic with backoff and exhausted messages to a DLQ for investigation.
  • A queue moves load through time; it does not remove it. Monitor lag, oldest-message age, and throughput, and define backpressure plus an explicit drop/degradation policy before the buffer fills.

A database transaction can atomically deduplicate and apply a database change; it cannot atomically include an arbitrary external email or payment API. External effects need their own idempotency contract or a recoverable handoff.

Sources: Reference 1, Reference 2.

13

Indexes and denormalization

Short answer: Indexes speed matching queries at the cost of storage and write work. Denormalization keeps extra copies for reads; each copy needs a source of truth, update mechanism, acceptable lag, and reconciliation strategy.

  • Indexes speed up reads at the cost of slower writes and extra space. Create them for your actual queries (WHERE/JOIN/ORDER BY).
  • Denormalization: duplicating data to avoid JOINs on reads. Use it in read-heavy systems (feed, profiles). Trade-off: harder to keep consistent on writes.

Start from an access pattern: an index on (tenant_id, created_at DESC) serves a particular filter and ordering, while indexing everything increases write amplification and maintenance time. For each denormalized copy name the source of truth and update mechanism—same transaction, outbox/CDC, or periodic rebuild—and define the acceptable stale window plus reconciliation after a missed event.

write → source of truth → outbox/CDC → read model
                                  ↘ retry + reconciliation
14

CAP theorem and consistency

Short answer: During a network partition, a system cannot guarantee both linearizability and successful responses to every request at non-failing nodes. Make the trade-off for specific operations and invariants; CAP is not a permanent choice of any two letters.

CAP is about behavior during a network partition. A partition prevents some nodes from communicating. In that situation, a system cannot guarantee both linearizable operations and a successful response to every request at every non-failing node.

  • C here means a single operation history consistent with real-time ordering, not the broad idea of a correct business result.
  • A means requests to non-failing nodes can complete successfully; rejecting an operation to preserve consistency sacrifices CAP availability for that operation.
  • A consistency-preserving design may stop writes on a minority partition. An availability-oriented design may accept divergent writes and reconcile them after communication recovers.

Choose per invariant and operation, rather than labeling all databases or all banking systems CP/AP. Product configurations and read modes matter; DynamoDB, for example, offers multiple consistency modes.

PACELC highlights an additional question: even without a partition, what latency/consistency trade-off does replication impose? Neither acronym replaces checking the actual protocol, failure behavior and documented guarantees.

Sources: Reference 1, Reference 2.

15

Consistency: strong vs eventual, quorums

Short answer: Linearizability gives operations a single order consistent with real time; eventual consistency promises convergence once updates stop and replicas can exchange data. W + R > N gives quorum overlap for a fixed replica set, but does not alone prove linearizability.

Linearizability makes each operation appear to take effect at one instant between its call and return. A read after a completed write observes that write or a later one. Concurrent operations still need an ordering rule.

Eventual consistency promises convergence once updates stop and replicas can exchange the necessary information. It does not by itself specify a maximum stale interval, monotonic reads or preservation of every concurrent update; conflict resolution matters.

Quorum overlap: with a fixed replica set of N nodes, a write to W and a read from R overlap when W + R > N. For N=3, W=2, R=2, at least one read replica intersects the write set. The protocol must still select the appropriate version and handle concurrent or partially completed writes, failures and membership changes.

Pitfall: overlap alone is not a proof of linearizability. Sloppy quorums, timestamp-based last-write-wins and concurrent writers can invalidate a simple “fresh read guaranteed” claim. Specify the database, consistency level and operation; use its documented guarantees and test the failure cases your invariant depends on.

Sources: Reference 1, Reference 2.

16

Idempotency and dedup

Short answer: Idempotency preserves the same intended effect when an operation is repeated. Bind the key to the caller and payload, atomically store the business effect and result, and define retention. External effects need their own idempotency contract or a recoverable handoff.

  • Idempotency: repeating an operation has the same intended effect on server state (a retried payment POST doesn't charge twice).
  • Implementation: an idempotency key from the client → the server stores the result by key and, on a retry, returns the saved result without performing the operation again. Store it in Redis/DB with a TTL.
  • Message dedup in queues — by message_id.

The key check and business effect must be atomic. A reliable design stores an idempotency_key row with a UNIQUE constraint in the same database transaction; checking Redis separately before the write leaves a race window. Persist the status and serialized response, reject the same key with a different payload, and retain it longer than the maximum client retry window.

same key + same payload → same stored result
same key + other payload → 409 conflict

Scope the key to the authenticated caller. A local database transaction cannot include an external payment provider: propagate a stable provider idempotency key or use a durable, recoverable handoff. Do not blindly retry an operation whose outcome is unknown.

Sources: Reference 1.

17

Rate Limiting

Short answer: A rate limiter bounds load by user, key, tenant, or IP. Choose a window algorithm or token bucket, update shared state atomically, and return 429 when exceeded. Decide in advance how a limiter-store outage affects requests.

  • Rate limiting protects capacity and constrains abuse. Fixed window is simple but permits a double burst at a boundary; sliding window/log is more accurate but stores more state; token bucket permits a controlled burst and replenishes at a steady rate.
  • Key the limit by user, API key, tenant, or IP and return 429 with Retry-After. Perform it before expensive work but after identifying the caller well enough.
  • In a distributed system, keep state in Redis and update it atomically with Lua. Decide whether a Redis outage fails open for a noncritical API or fails closed for an expensive or sensitive operation.
request → identify key → consume token? ─ yes → handler
                                  └ no  → 429 + Retry-After
18

Microservices vs monolith

Short answer: A modular monolith simplifies local calls and deployment. Microservices allow independent deployment and scaling but add network and distributed-state costs. Both designs require deliberate fault isolation and transaction boundaries.

  • Monolith: a single deployment can simplify local calls, development and debugging; related operations can share a database transaction when designed that way. It is a useful starting point for a new product.
  • Microservices: can support independent scaling and deployment per team; fault isolation requires deliberate dependency and resource boundaries. The cost: network calls, distributed transactions (saga), complex observability, eventual consistency between services.
  • 💡 In an interview: "I'd start with a modular monolith and split out into services whatever needs to scale independently or is owned by a separate team."

A monolith does not automatically put all work in one transaction, and microservices do not automatically isolate faults. Transaction boundaries, dependency timeouts and resource isolation must be designed in either architecture.

Sources: Reference 1.

19

API Gateway

Short answer: An API gateway centralizes routing and common policies such as authentication checks, rate limits, and TLS. It does not replace resource-level authorization in the owning service. Design timeouts, partial-failure behavior, observability, and gateway redundancy.

  • A single entry point: routing, authentication, rate limiting, response aggregation, versioning, TLS. Offloads cross-cutting concerns from the services. The risk is becoming a SPOF/bottleneck, so it's scaled and made redundant.

Do not turn the gateway into a new business-logic monolith. It may validate a token and enforce common policy, but resource-level authorization belongs to the service that owns the data. Run multiple stateless instances behind a load balancer, use short downstream timeouts, cap request bodies, and observe latency/error rate per route. Response aggregation also needs partial-result and fallback behavior when one downstream fails.

client → gateway ┬→ users
                 ├→ orders
                 └→ catalog
20

Blob Storage (S3)

Short answer: Object storage keeps files by key, while metadata commonly stays in a database. Clients can upload through a time-limited signed URL and read through a CDN. Validate uploads before publication; object storage is not a normal filesystem.

  • Object storage for files, images, video, backups. Cheap, durable (internal replication), highly scalable within service limits, quotas and workload constraints.
  • Pattern: metadata in the DB, the file itself in S3, served via CDN, uploaded via a pre-signed URL (the client writes to S3 directly, bypassing the backend).

An object is addressed by key and normally replaced as a whole; object storage is not a POSIX filesystem and is unsuitable for frequent in-place updates. Versioning and lifecycle rules can archive or delete old objects. A pre-signed URL should have a short lifetime, a restricted method, and an unpredictable key; after upload, verify size and file signature and run asynchronous malware scanning before making it public.

client ──metadata──► API ──signed URL──► client
client ─────────────upload──────────────► object storage

Sources: Reference 1.

21

Search (Elasticsearch)

Short answer: A search index maps terms to documents and supports full-text queries, filters, and aggregations. In this design it is updated from the primary database through CDC or a queue; updates need idempotency, versioning, and a rebuild path.

  • Full-text search, facets, aggregations (an inverted index). Not the primary store — data comes from the main DB via CDC/a queue. Eventual consistency is acceptable.

An inverted index maps a term to matching documents; the configured analyzer may tokenize, normalize and stem text. Design mappings deliberately: text fields support full-text relevance, while keyword fields support exact filters and aggregations. A common write path is DB → outbox/CDC → indexer → search index; events must be idempotent and versioned, and periodic reconciliation repairs missed updates.

⚠️ Do not make Elasticsearch the sole source of truth for payments or inventory. Refresh and replication create a stale window, and rebuilding an index is a normal operational event.

Sources: Reference 1.

22

Geo-distribution and latency

Short answer: Placing reads and content closer to users can reduce latency, but cross-region writes complicate consistency. Define key ownership, write routing, and conflict resolution, then choose the latency and guarantees the operation requires.

  • Place services and data closer to users (multi-region), use a CDN and GeoDNS / Anycast.
  • The speed of light sets a lower bound on latency: an intercontinental round-trip is ~100-200 ms. Within a datacenter — < 1 ms.
  • Trade-off: multi-region complicates consistency (geo-distributed writes → conflicts).

Separate reads from writes first: static content and read replicas can move closer to users, while writes stay in one home region to preserve simple ordering and transactions. Active-active writes need key ownership or conflict resolution; last-write-wins can lose a valid update because wall clocks disagree. For money or unique inventory, route a key to one leader and accept the cross-region delay.

user → nearest edge/read replica
write → home region leader → async replicas
23

SPOF and fault tolerance

Short answer: Look for shared dependencies that can stop the system, not just single servers. Pair redundancy with tested failover, timeouts, safe retries, and controls against cascading failures. Measure recovery, not merely replica counts.

  • A Single Point of Failure is a component whose failure brings down the system. It's eliminated by redundancy: multiple instances behind an LB, DB replication with failover, multi-AZ.
  • Resilience patterns: health checks, retries with backoff, circuit breaker, graceful degradation (serve the cache/a partial response), timeouts, bulkheads.

Check shared dependencies, not only instance count: two application replicas in one AZ, one DNS service, a shared connection pool, or manual failover can still be a SPOF. Retry only safe/idempotent operations with exponential backoff, jitter, and an overall deadline. Circuit breakers constrain a sick dependency, bulkheads keep it from consuming every worker, and graceful degradation preserves the critical path.

⚠️ A replica without a regularly tested failover is a copy, not fault tolerance. Run game days and measure actual RTO and RPO.

24

Scale metrics (orders of magnitude)

Short answer: Keep units explicit: average QPS is daily requests / 86,400, bandwidth is QPS × response size, and raw storage is records × record size. Test server capacity with the target workload and latency; no universal QPS figure applies.

Estimate with units and explicit assumptions, then measure. There is no universal QPS capacity for “one Go server”, “one database” or “one Redis instance”. Query cost, payloads, indexes, durability, concurrency, hardware and tail-latency targets can change capacity by orders of magnitude.

Useful arithmetic:

Quantity Calculation
Average requests/second requests/day ÷ 86,400
1 million requests/day about 11.6 requests/second
Payload bandwidth requests/second × bytes/response
Raw stored bytes records × average encoded record size

For example, 1,000 responses/second at 10 KB each is about 10 MB/second, or 80 Mbit/second, before protocol overhead. Storage estimates must then include indexes, replicas, backups and retention.

State the assumed peak factor instead of presenting one as a law. Benchmark a representative workload at the required p95/p99 latency and error rate, include spare capacity for failures, and report the environment with the result. Network and storage latency numbers are also measurements, not guarantees of a component name.

Sources: Reference 1, Reference 2.

25

URL Shortener (TinyURL)

Short answer: For a URL shortener, store a unique key, destination, owner, and expiry. Choose predictable or random keys from requirements, handle collisions atomically, and check expiry on reads. Cache and redirect policies must support freshness and revocation needs.

Design a URL shortener from its guarantees. Clarify creation, redirects, custom aliases, expiry and abuse controls. Decide whether links must be hard to guess; uniqueness alone does not provide that.

Scale example: 100 million creations per month is about 40 writes/second. At 100 redirects per creation, average read traffic is about 4,000/second. At 500 bytes per record, raw growth is 50 GB/month, before indexes, replicas and backups. These are assumptions for the exercise.

API and data: POST /urls creates a link; GET /{key} looks it up and redirects. Store key as a unique primary key alongside destination, owner and expiry. Validate permitted URL schemes and check expiry during reads; background deletion alone is not prompt expiry enforcement.

Keys: counter-to-base62 is compact and unique with correctly allocated non-overlapping ranges, but predictable. If unpredictability matters, generate random keys from a cryptographically secure source and retry after a unique-constraint conflict. A hash prefix can also collide; check-and-insert must be atomic. Seven base62 characters provide about 3.5 trillion possible values, not a collision-free allocation guarantee.

Reads: start with an indexed database lookup, add a cache when measurements justify it, and bound cache lifetime by link expiry. Support immediate use of newly created links even if some replicas lag.

Redirect caching: 301 may be cached heuristically; 302 can also be cached when explicit freshness permits. Neither “301 forever” nor “302 always reaches origin” is guaranteed. Choose status and Cache-Control together, considering editable destinations, expiry and analytics.

Sources: Reference 1, Reference 2.

26

News Feed

Short answer: A news feed can gather posts on read or precompute followers’ feeds on write. Push makes reads cheaper at the cost of fan-out; pull makes posting cheaper at the cost of reads. A hybrid can handle authors with very large audiences.

1. Requirements. Functional: a user sees a feed of posts from the people they follow, sorted (by time/relevance); publishing a post; follows. NFR: read-heavy; low feed-load latency (this is the hot path); eventual consistency is fine (a couple of seconds' delay before a post appears is acceptable); high availability.

2. Scale. Say 300M DAU, each opening the feed ~10 times/day → 3B reads/day ≈ 35k QPS, peak ~70k. Posts: 100M/day. Average number of follows ~ hundreds; for celebrities — tens of millions of followers (an important special case).

3. API.

GET  /v1/feed?cursor=...&limit=20      -> [posts]
POST /v1/posts {content, media_ids}    -> {post_id}
POST /v1/follow {target_user_id}

Pagination — cursor-based (by post_id/timestamp), not offset.

4. Data.

  • posts(post_id, author_id, content, media, created_at)
  • follows(follower_id, followee_id)
  • feed_cache (precomputed): a per-user list of post_ids (Redis list / sorted set). SQL for the follow graph and posts; Redis for the materialized feeds.

5. Fan-out — the key decision 🔴:

  • Fan-out on write (push): when a post is published, immediately write its id into the feeds (Redis) of all followers. Reading the feed → a fast GET of a ready list. Pro: lightning-fast reads. Con: expensive writes for popular authors (the "fan-out problem": a celebrity's post with 50M followers = 50M writes).
  • Fan-out on read (pull): assemble the feed at request time — take posts from everyone you follow and merge them. Pro: cheap writes. Con: expensive and slow reads, especially with many follows.
  • Hybrid (one option for this workload): regular users — push; celebrities (above a follower-count threshold) — pull. On read, merge the precomputed feed with the "live" posts of the celebrities you follow. Choose the split from measured fan-out cost and freshness requirements.

6. Architecture.

Post -> API -> posts DB -> [Queue] -> Fan-out workers -> Redis feed lists
Read feed -> API -> Redis (ready feed) + pull celebrity posts -> merge -> rank

Fan-out is asynchronous via a queue so it doesn't block publishing.

7. Ranking and bottlenecks.

  • Ranking: chronological is simple; ML ranking (relevance) is a separate service with scores; mentioning it is enough in an interview.
  • Bottlenecks: celebrity fan-out (→ hybrid), Redis memory (store only the N most recent post_ids per user, the rest is a pull from the DB), hot keys.
  • Fault tolerance: the feed cache can be rebuilt from the DB; Redis replicas.

The example peak factor and celebrity threshold require measurement. Include authorization, deletion and privacy changes in feed invalidation: a cached post ID is not proof the current viewer may still read the post.

Sources: Reference 1.

27

Chat / messenger (WhatsApp-like)

Short answer: Separate persistent connections, durable storage, and acknowledged delivery in a chat service. Store messages before delivery and handle retries and offline recipients. Strict per-chat ordering needs an explicit protocol, not just timestamp-based IDs.

1. Requirements. Functional: 1-on-1 messages, group chats, delivery statuses (sent/delivered/read), presence (online/last seen), history. NFR: low delivery latency; high availability; message durability (don't lose them); message ordering within a chat; scale of connections.

2. Scale. 500M DAU, 40 messages/day → 20B messages/day ≈ 230k msg/sec. Concurrent connections — hundreds of millions. Storage: 20B × ~200 B ≈ 4 TB/day.

3. API / protocol. Not classic REST for delivery — you need bidirectional communication: WebSocket (or MQTT) for live, plus HTTP for history and registration.

WS: connect; send {chat_id, msg}; receive {msg}; ack {msg_id}
GET /v1/chats/{chat_id}/messages?cursor=...

4. Data.

  • messages(chat_id, msg_id, sender_id, content, created_at, status) — partitioned/sharded by chat_id, sorted by time. Wide-column (Cassandra) is ideal: huge write volume, access by chat key, time-series. A Snowflake-style ID is roughly time-ordered, not a guarantee of causal or total order across writers. Assign a per-chat sequence with a serialized owner or suitable database protocol when strict chat order is required.
  • chats, chat_members, user_presence.

5. Delivery — the key part 🔴:

  • Users hold a persistent WebSocket connection to gateway servers (stateful). You need a registry: which user is on which gateway → store it in Redis (user_id -> gateway_id).
  • Sending: A writes to its gateway → the message service saves it to the DB (durability) → finds recipient B's gateway → pushes over B's WS. B sends an ack → status becomes delivered.
  • B offline: the message sits in the DB/queue; when B connects, it requests undelivered messages (after the last contiguous acknowledged server chat sequence). A push notification (APNs/FCM) is an option.
  • Groups: fan out the message to members (for large groups — like feed fan-out).

6. Presence. A heartbeat over WS every N seconds → update last_seen in Redis with a TTL. No heartbeat → offline. Don't push presence to everyone — only to active observers.

7. Bottlenecks.

  • Millions of persistent connections: many gateway servers, a connection LB (L4, sticky). Measure connections per gateway under the actual memory, heartbeat and message load.
  • Routing between gateways → a Redis registry + pub/sub.
  • Ordering and idempotency: a client-generated msg_id for dedup on retries.
  • Durability: write to the DB before acknowledging the sender.

Use a separate client request ID for deduplication and a server-assigned chat sequence for ordering. Store delivery/read acknowledgments per recipient/device when needed; one status column is insufficient for groups. Concurrent connections are a separate workload assumption, not derivable from DAU alone.

Redis Pub/Sub is at-most-once notification, so it cannot be the sole delivery record. Keep messages and recipient cursors durable; recover gaps from the log on reconnect or through an explicit retry/synchronization path. For multiple devices, track each active device/session rather than overwriting one user-to-gateway slot. Documentation.

Sources: Reference 1, Reference 2.

28

Rate Limiter

Short answer: For a distributed rate limiter, define the key, quota, allowed burst, and outage policy. Choose fixed/sliding windows or a token bucket and make checking plus updating shared state atomic. Explain the accuracy, memory, and latency trade-off.

1. Requirements. Limit the number of requests per client (user/IP/API key) over a period: for example 100 req/min. NFR: low overhead and latency; accuracy; works in a distributed environment (many instances); fail-open or fail-closed when the storage fails (discuss).

2. Scale. It runs on every request → it must be very fast (< 1 ms). At 100k QPS — 100k checks/sec.

3. API. Usually middleware / part of the API Gateway. allow(key) -> bool. On rejection → HTTP 429 + headers X-RateLimit-Remaining, Retry-After.

4. Algorithms (the central part 🟡):

  • Fixed window counter: a counter per window (a minute). Simple, but a burst at the window boundary (up to 2× the limit).
  • Sliding window log: store the timestamp of every request, count over the last 60 sec. Accurate, but expensive in memory.
  • Sliding window counter: interpolation between the current and previous window. A good balance of accuracy and memory. A common choice.
  • Token bucket: the bucket fills with N tokens/sec, a request takes a token; empty → rejection. Allows bursts up to the bucket size. Very popular (flexible).
  • Leaky bucket: requests go into a queue, processed at a constant rate. Smooths out traffic.

5. Distributed implementation 🔴:

  • State is shared across all instances → Redis (fast, atomic). Counter/tokens by key rl:{user}:{window} with a TTL.
  • Atomicity — a Lua script (read-check-write in one operation) or an appropriate transaction; separate INCR and EXPIRE commands are not atomic together. Without atomicity — races and overshooting the limit.
  • Latency trade-off: a network call to Redis on every request. Optimization — a local cache + periodic synchronization (sacrificing a bit of accuracy).
  • When Redis is unavailable: fail-open (let requests through so as not to take down the service) vs fail-closed (block) — decide by requirements.

6/7. Bottlenecks. Redis as a hot spot → shard authoritative state by client key; replicas support recovery, not independent admission decisions. A hot key from one large client — a dedicated shard / local limiting.

For a fixed-window counter, set expiry when the key is created rather than extending it on each request. Replica lag/failover and local counters can permit overshoot: define a bound, or use an authoritative design when the limit must be strict. The sub-millisecond target is a design goal to measure, not a Redis/network guarantee.

Sources: Reference 1.

29

File / video storage and serving

Short answer: Keep file metadata in a database and content in object storage, with CDN delivery. Use resumable uploads for large files and queue-based workers for transcoding. Check access, size, and content before publication.

1. Requirements. Functional: upload a file/video, store it, download/stream it, (for video) transcode into different qualities. NFR: durability (don't lose files); high throughput; low serving latency globally; large volume.

2. Scale. Say 1M uploads/day, average file 5 MB → 5 TB/day of new content; at 20 full downloads per upload, this is 100 TB/day, about 9.3 Gbit/second on average. Peak traffic and geography determine the serving design; a CDN can reduce origin load.

3. API.

POST /v1/files/initiate {filename, size, content_type} -> {upload_id, presigned_url}
PUT  <presigned_url>  (client uploads directly to S3, multipart for large files)
POST /v1/files/complete {upload_id} -> {file_id, url}
GET  /v1/files/{file_id} -> 302 -> CDN URL

4. Data.

  • Metadata in the DB: files(file_id, owner, name, size, s3_key, status, created_at) — a relational DB is fine.
  • The content itself — blob storage (S3), not the DB. Large files — multipart upload (chunks, in parallel, resumable).

5. Architecture (the central part 🟡):

Client --presigned PUT--> S3 (origin)
       <--metadata-- App Server -- DB
S3 --event--> Queue --> Transcoding Workers --> S3 (variants 240p/720p/1080p)
Client --GET--> CDN (edge) --miss--> S3
  • Pre-signed URL: the backend issues a temporary URL, the client writes straight to S3, bypassing the app servers (we don't push terabytes through the backend).
  • Video transcoding: an upload triggers an event → a queue → a pool of workers slices it into segments and encodes it into several qualities/bitrates (for adaptive streaming, HLS/DASH). Asynchronous, idempotent.
  • CDN serves both static assets and video segments — the main read layer.

6/7. Bottlenecks and resilience.

  • Read bandwidth → the CDN takes the bulk of the load off the origin.
  • Durability → select an S3 storage class with the required multi-AZ resilience; evaluate cross-region replication for regional recovery needs.
  • Transcoding is CPU-heavy → scale workers by queue length (autoscaling); retries on failures; task status in the DB.
  • Large uploads → multipart + resume + checksum verification.
  • Deduplication by content hash (store identical files once).

Authorize every upload/download and validate final object size, checksum and state before marking it ready. S3 event notifications can be duplicated and arrive out of order, so workers deduplicate by object/version and persist state transitions. Multi-AZ durability depends on the selected storage class; it is not a property of every S3 class.

Sources: Reference 1, Reference 2.

Source notes

References and review policy

Source links provide further reading. Where a recorded technical review is available, the answer shows its date and scope. Tool selections use direct provider links and contain no affiliate placements. Sources, features, and hiring formats can change.

From reading to recall

Practice the full interview loop.

RecallDeck schedules the concepts you miss and keeps coding, design, and behavioral fundamentals available when the interviewer changes direction.

Start studying

Keep going