Interview prep

Interview prep · 17 min

Quick concept questions

Short answers to the conceptual questions interviewers ask in between.

Between the big design questions, interviewers often fire off short conceptual ones: "What's the difference between a queue and pub/sub?" or "When would you pick write-back caching?" They want a crisp answer in under a minute, ideally with a trade-off and an example.

Each answer below is written to be said out loud in about that time. Read one, close the page, and try saying it in your own words. Then follow the "Learn more" link for the full story.

These answers use terms from the whole course, so if you're early on, some will be dense. That's expected: start with the sections whose topics you've already reached, and treat the rest as a checklist for later. The "Learn more" link under each answer says where it's taught.

Data and storage

SQL or NoSQL?

Start from the data and the queries, not the brand. Relational databases (Postgres, MySQL) shine when data has relationships, you need joins and ad-hoc queries, and you want transactions across several rows. That covers most business data: orders, users, payments. NoSQL is a family, not one thing. Key-value and wide-column stores (DynamoDB, Cassandra) trade joins and flexible queries for easy horizontal scaling and predictable latency at huge write rates, but you must design tables around known access patterns. Document stores fit nested, self-contained records. A good interview answer: "I'd default to Postgres, and move a specific workload to Cassandra or DynamoDB if its write rate or size outgrows one machine and its queries are simple key lookups."

Learn more: SQL or NoSQL?

B-tree or LSM tree?

Both are ways to organise an index on disk. A B-tree keeps sorted data in fixed-size pages and updates them in place. Reads are fast and predictable (a few page reads), but each random write may rewrite a whole page. An LSM tree buffers writes in memory, flushes them as sorted immutable files, and merges those files in the background (compaction). Writes become fast sequential appends, but a read may have to check several files, which Bloom filters help with, and compaction uses disk bandwidth. Rule of thumb: B-trees (Postgres, MySQL InnoDB) for read-heavy, mixed workloads; LSM trees (Cassandra, RocksDB) for write-heavy workloads like logs, metrics and messages.

Learn more: B-trees and LSM trees

Sharding, partitioning and replication: what's the difference?

Replication means keeping copies of the same data on several machines. It buys availability and read capacity, but every copy still holds all the data. Partitioning means splitting the data into pieces so each piece holds only some of it. When those pieces live inside one database (one big table split by month, say), people usually call it table partitioning. When the pieces live on different database servers, it's called sharding. Real systems combine them: data is sharded across ten machines, and each shard is replicated three times. Sharding is the step you put off longest, because cross-shard queries and transactions get hard.

Learn more: Partitioning a table, Sharding and Why replicate?

What are isolation levels?

When transactions run at the same time, isolation decides how much they can see of each other. Read committed (the default in Postgres) only shows committed data, but the same query run twice can return different results. Snapshot isolation (repeatable read in Postgres) gives each transaction a consistent snapshot, but still allows write skew: two doctors each check "is someone else on call?", see yes, and both go off call. Serializable behaves as if transactions ran one at a time, at the cost of more aborts or blocking. In an interview, name the anomaly you are worried about and pick the weakest level that prevents it, or use an explicit lock for that one operation.

Learn more: Isolation levels

Optimistic or pessimistic locking?

Pessimistic locking takes the lock first: SELECT ... FOR UPDATE blocks anyone else from changing the row until you commit. It's safe and simple when conflicts are common, like many people grabbing the last few seats, but holding locks hurts throughput and can deadlock. Optimistic locking takes no lock. You read a version number, do your work, and update with WHERE version = 7. If someone changed the row in between, zero rows update and you retry. It's great when conflicts are rare, like editing your own profile, and wasteful when they are frequent, because most attempts get retried. Pick by how often two writers really collide.

Learn more: Pessimistic and optimistic locking

Read replicas and replication lag

A read replica is a copy of the primary database that serves reads, taking load off the primary. Replication is usually asynchronous, so a replica can be a few milliseconds, or during heavy load several seconds, behind. The classic bug: a user places an order, the next page reads from a lagging replica, and the order "vanished". Fixes include reading your own recent writes from the primary (for example for a few seconds after a write), pinning a user's session to one replica, or waiting until the replica has caught up to a known position. Replicas scale reads only. Every write still goes to the primary.

Learn more: Read replicas and Replication lag

Consistency and coordination

CAP and PACELC

CAP says that when a network partition splits your replicas, you must choose: either refuse some requests to stay consistent (CP), or keep answering and risk returning stale or conflicting data (AP). It is not "pick two of three", because partitions are not optional. They happen. PACELC adds the part CAP leaves out: else, when there is no partition, you still trade latency against consistency, because waiting for replicas to agree takes time. So DynamoDB and Cassandra in their default modes are PA/EL (available, low latency), while Spanner is PC/EC. In interviews, say which data needs which: a bank balance wants consistency, a like count can be eventual.

Learn more: Consistency models

Strong or eventual consistency?

With strong consistency (more precisely, linearizability), every read sees the latest completed write, as if there were one copy of the data. It usually means routing reads through a leader or a quorum, which costs latency and availability during failures. With eventual consistency, replicas may disagree for a while, but if writes stop, they converge. Reads are fast and always available, but you might see old data or your own write might seem to disappear. Many systems sit in between: read-your-writes or causal consistency give users sensible behaviour without the full cost. Pick per feature: inventory and payments strong, feeds and counters eventual.

Learn more: Consistency models

What is consistent hashing?

With plain hash(key) % N, adding one server changes N, so almost every key moves to a different server, which empties every cache at once. Consistent hashing places both servers and keys on a ring of hash values. Each key belongs to the next server clockwise. Adding a server only takes over the keys between it and the previous server on the ring, roughly 1/N of the data, and removing one hands its keys to the next server clockwise. To avoid uneven arcs, each physical server gets many virtual nodes scattered around the ring. Used by Dynamo-style stores, Cassandra, and client-side cache sharding.

Learn more: Consistent hashing and The consistent hash ring

How does leader election work (Raft)?

Many systems want exactly one node making decisions, so they elect a leader. In Raft, every node starts as a follower and expects regular heartbeats from the leader. If a follower hears nothing for a randomised timeout, it becomes a candidate, increments the term number and asks for votes. Each node votes at most once per term, and only for a candidate whose log is at least as up to date as its own. Whoever gets a majority wins. Because two majorities always overlap, there can be only one leader per term. A stale leader that comes back sees a higher term and steps down. Random timeouts make split votes rare.

Learn more: Raft: leader election

Saga or two-phase commit?

Both deal with one business action that writes to several services or databases. Two-phase commit (2PC) has a coordinator ask every participant to prepare, then tells all of them to commit. It gives real atomicity, but participants hold locks while waiting, and if the coordinator dies at the wrong moment they are stuck. A saga runs a chain of local transactions, each committed immediately, and if a later step fails it runs compensating actions (refund the payment, release the stock). Sagas don't block and suit microservices, but intermediate states are visible and compensations must be designed carefully. Most service architectures use sagas. 2PC lives mostly inside databases.

Learn more: Two-phase commit and Sagas

What is a Bloom filter?

A Bloom filter is a compact bit array that answers "is this item in the set?" with either "definitely not" or "probably yes". Adding an item sets k bits chosen by k hash functions. Checking an item looks at those same k bits. If any is 0, the item was never added. If all are 1, it probably was, but other items may have set those bits (a false positive). There are no false negatives. About 10 bits per item gives roughly a 1% false positive rate. Uses: LSM trees skip files that can't hold a key, crawlers skip URLs already seen, and caches avoid lookups for keys that don't exist. You can't delete from a basic Bloom filter.

Learn more: Bloom filters

Caching, scaling and traffic

Cache-aside, write-through or write-back?

Cache-aside: the app checks the cache, on a miss reads the database and stores the result. Simple, and only data that's actually read gets cached, but the first read is slow and you must invalidate on writes. Write-through: every write goes to the cache and the database together, so the cache is always fresh, but writes are slower and you cache data nobody may read. Write-back: writes go to the cache only, and are flushed to the database later. Writes are very fast, but if the cache dies before flushing, data is lost. Most web apps use cache-aside with TTLs. Write-back fits counters and metrics where losing a few seconds is acceptable.

Learn more: Cache-aside and Write-through and write-back

What is a cache stampede?

A popular key expires, and in the same instant hundreds of requests miss the cache and all hit the database to rebuild the same value. The database falls over, which makes every request slower, which makes things worse. Fixes: let only one request rebuild the value while the others wait or get the stale copy (a lock or "single flight"), refresh popular keys a little before they expire, and add random jitter to TTLs so many keys don't expire together. The same family of problems includes hot keys (one key gets too much traffic for one cache node) and cache penetration (repeated lookups for keys that don't exist).

Learn more: When caches go wrong

L4 or L7 load balancing? Which algorithm?

An L4 balancer works at the TCP level. It sees IPs and ports, forwards connections without reading them, and is very fast. An L7 balancer reads HTTP, so it can route by path or header, terminate TLS, retry failed requests and do sticky sessions, at more CPU cost. Common algorithms: round robin (fine when requests are similar), least connections (better when request times vary), weighted versions for mixed machine sizes, and hashing on a key when the same client should hit the same server. Health checks matter as much as the algorithm: a balancer that keeps sending traffic to a dead server is worse than none.

Learn more: Load balancers

Vertical or horizontal scaling?

Vertical scaling means a bigger machine: more CPU, more RAM, faster disks. No code changes and no distributed-systems problems, and a single modern server goes much further than people expect. But there's a ceiling, prices rise steeply near the top, and it's still one machine that can fail. Horizontal scaling means more machines behind a load balancer. It's nearly unlimited and survives individual failures, but it requires stateless servers and brings new problems: shared sessions, coordination, and data that must be split or replicated. Usual path: scale up first, make the app tier stateless and scale it out, and leave the database for last.

Learn more: Scaling up and Scaling out

How does a CDN work?

A CDN is a network of servers (edges) in many cities. DNS or anycast sends each user to a nearby edge. The edge serves the file from its cache if it has it. If not, it fetches it from your server (the origin), stores it and serves it. Popular files are then served from a few milliseconds away, and your origin sees a tiny fraction of the traffic. You control behaviour with cache headers, TTLs and cache keys, and you can purge content when it changes. Best for static and shared content: images, video segments, scripts. Personalised responses usually aren't cacheable, though edges can still speed up TLS and routing for them.

Learn more: CDNs

APIs and communication

Polling, long polling, SSE or WebSockets?

Polling: the client asks "anything new?" every few seconds. Simple and robust, but wasteful, and updates lag by up to one interval. Long polling: the server holds the request open until there is news, then the client immediately asks again. Near real-time over plain HTTP. Server-sent events: one long-lived HTTP response that streams events from server to client, with automatic reconnect. Great for one-way feeds like live scores. WebSockets: a persistent two-way connection, needed when the client also sends frequently, like chat or games. The harder question across all of them is routing: which server holds this user's connection?

Learn more: Polling, WebSockets and Live updates across many servers

REST, gRPC or GraphQL?

REST models resources with URLs and HTTP verbs, uses JSON, and works everywhere: browsers, curl, caches and CDNs. It's the default for public APIs. gRPC uses Protobuf over HTTP/2: compact binary messages, generated typed clients, and streaming. It's faster and stricter, which suits service-to-service calls, but browsers need a proxy and payloads aren't human-readable. GraphQL exposes one endpoint where the client asks for exactly the fields it needs, which avoids over-fetching and many round trips for complex screens. The costs are harder caching and the risk of expensive queries. Common setup: REST or GraphQL at the edge, gRPC inside.

Learn more: REST, gRPC or GraphQL?

What makes an API idempotent?

An operation is idempotent if doing it twice has the same effect as doing it once. GET, PUT and DELETE are meant to be idempotent. POST /payments is not: a retry after a timeout could charge twice. The standard fix is an idempotency key: the client generates a unique ID for the attempt and sends it with every retry. The server stores the key with the result. If it sees the key again, it returns the saved result instead of doing the work again. The key must be stored atomically with the work, or a crash between them breaks the guarantee. Any API that clients may retry, which is all of them, should think about this.

Learn more: Idempotency keys

How do rate limiting algorithms differ?

Fixed window: count requests per calendar minute. Simple, but a client can send a full burst at 12:00:59 and another at 12:01:00, doubling the limit. Sliding window log: store each request's timestamp and count the last 60 seconds. Accurate, but memory grows with traffic. Sliding window counter: blend the current and previous window's counts by weight. Close to accurate with two numbers. Token bucket: tokens refill at a steady rate up to a cap, and each request spends one. It allows short bursts while enforcing an average, which is why it's the most common choice. Leaky bucket smooths output to a fixed rate. Across many servers, keep counters in a shared store like Redis.

Learn more: Fixed and sliding windows and Token bucket

Monolith or microservices?

A monolith is one deployable app. It's simple to build, test, debug and deploy, function calls are fast, and transactions just work. Microservices split the app into independently deployed services, each owning its data. That lets many teams ship without stepping on each other and lets you scale or fail parts independently. The price: network calls that can fail, distributed transactions, harder debugging, and much more infrastructure. The usual advice is to start with a well-structured monolith and split off services when team size or very different scaling needs make the pain of one codebase greater than the pain of distribution.

Learn more: Monolith first

Messaging and streams

Queue or pub/sub?

In a queue, each message is processed by exactly one consumer. Workers compete for messages, which spreads out work like sending emails or resizing images. In pub/sub, each message is delivered to every subscriber. One "order placed" event goes to billing, notifications and analytics, each getting its own copy. Kafka does both: within a consumer group, partitions are split among consumers (queue-like), and different groups each read the full stream (pub/sub-like). Use a queue to distribute tasks, pub/sub to broadcast facts that several services care about.

Learn more: Queues and pub/sub

How does Kafka work?

Kafka is a distributed, append-only log. A topic is split into partitions, and each partition is an ordered log stored on disk and replicated to several brokers, with one leader per partition. Producers append messages, and the message key decides the partition, so all events for one order stay in order. Consumers read sequentially and track their position with an offset. Messages aren't deleted when read. They're kept for a configured time, so new consumers can replay history. Consumers in a group split the partitions between them, so the partition count caps how many can work in parallel. With acks=all, a write is confirmed only once all in-sync replicas have it.

Learn more: How Kafka works and Consumer groups

At-least-once or exactly-once?

At-most-once: acknowledge before processing. If you crash, the message is lost. At-least-once: acknowledge after processing. If you crash in between, the message is delivered again, so you process it twice. That's what most systems use. True exactly-once delivery over an unreliable network isn't possible. What systems offer is exactly-once processing: at-least-once delivery combined with deduplication or idempotent handling, so duplicates have no extra effect. Kafka's transactions give this when reading from and writing to Kafka. Once you touch an outside system like a database or payment API, you need idempotency keys or a dedup table yourself.

Learn more: Delivery guarantees and Jobs that can run twice

Reliability and numbers

What are nines, SLOs and error budgets?

Availability is often written in nines. 99.9% ("three nines") allows about 43 minutes of downtime a month. 99.99% allows about 4.3 minutes, and 99.999% about 26 seconds. Each extra nine costs a lot more, in redundancy, automation and on-call effort. An SLI is what you measure (say, the share of requests that succeed within 300 ms). An SLO is your target for it (99.9% over 30 days). The gap between the target and 100% is the error budget: if you've used it up, you slow down releases and focus on reliability. Also remember that a chain of dependencies multiplies: three services at 99.9% each give roughly 99.7% together.

Learn more: SLIs, SLOs and nines

How do you do a back-of-the-envelope estimate?

Turn users into requests per second, then into storage and bandwidth, using round numbers. A day is about 100,000 seconds (86,400), which makes the arithmetic easy. Example: 10 million daily users each doing 20 reads gives 200 million reads a day, about 2,000 a second on average. Assume peaks are 2 to 5 times the average. If each write stores 1 KB and there are 1 million writes a day, that's 1 GB a day, about 365 GB a year. Then compare with rough capacities: a server handles thousands of simple requests a second, a Postgres primary a few thousand writes a second. The aim is the right order of magnitude, stated with units.

Learn more: Back-of-the-envelope numbers