System Design interview questions: Sharding
Reviewed by Mark Dickie · Last updated
Sharding is a horizontal partitioning technique that splits a large dataset across multiple database servers so each node holds only a subset of the rows. For interview purposes, you need to know the main sharding strategies (hash-based, range-based, directory-based, geo-based), the problems each one solves, and the trade-offs you inherit the moment you shard: cross-shard joins, distributed transactions, hotspot keys, and the cost of rebalancing when the cluster grows. Expect to explain when sharding is justified versus cheaper alternatives like read replicas, vertical scaling, or partitioning within a single node.
Most system design interviews that touch sharding follow the same arc: pick a partition key, justify it, describe how data is placed, then handle the operational consequences.
| Concept | What to know |
|---|---|
| Hash-based sharding | Apply a hash function to the partition key and mod by shard count. Distributes writes evenly but makes range scans hard and requires resharding when shard count changes. |
| Consistent hashing | Places shards on a hash ring so adding or removing a node moves only a fraction of keys. Reduces data movement during rebalancing compared to naive mod-N hashing. |
| Range-based sharding | Assign key ranges to shards (e.g. shard 1 owns IDs 0–9999). Good for range queries but prone to hotspots if traffic concentrates on one range. |
| Directory-based sharding | A lookup service maps each key to its shard. Flexible and reassignable, but the directory itself becomes a bottleneck and a single point of failure. |
| Geo-based sharding | Partition by region or location so user data stays close to the user. Common for global applications; complicates cross-region queries and consistency. |
What does a sharding interview question typically test?
- Choosing a partition key that avoids hotspots and keeps related data colocated.
- Explaining why a naive
key % Napproach breaks when you add a shard and how consistent hashing fixes it. - Handling cross-shard queries and distributed joins when a single user's data spans multiple shards.
- Describing the rebalancing process: how to split a shard, move data with minimal downtime, and route traffic during migration.
- Knowing when not to shard: if read replicas or vertical scaling solve the bottleneck, sharding adds complexity you may not need.
How do you pick a good shard key?
The shard key determines data distribution and query locality. A key with high cardinality and even write distribution (like a user UUID) spreads load well but scatters a single user's orders across shards if you shard on order_id. Sharding on user_id keeps a user's data together but can create a hotspot if one user generates disproportionate traffic. The interview answer is usually: pick a key that matches your dominant access pattern, has enough cardinality to distribute evenly, and avoids monotonically increasing values unless you pre-split ranges.
Key facts
- Tarmac has 12 System Design interview questions on this topic, 10 of them on this page, at difficulty 4–5 of 5.
- Tarmac tracked 4,937 job postings asking for System Design in August 2026.
- Roles asking for System Design advertise a median base salary of US$181,600, across 1,164 job postings as of August 2026.
- Tarmac last reviewed these System Design interview questions on 21 September 2026.
At a glance
| Questions | 10 shown · 12 in the bank |
|---|---|
| Difficulty | 4–5 of 5 |
| Formats | Multiple choice, Multiple answer, Short answer, Design exercise, True / false, Flashcard |
What you'll review
- sharding
- id generation
- resharding
- message queues
- caching strategies
- cap consistency
Practice questions
Try one before you open the answer. Pick an option and press Check; it's marked on the spot.
System Design/sd-data/sharding
You are sharding a high-write events table across many partitions. Using created_at (a monotonically increasing timestamp) as the shard key causes one shard to absorb nearly all writes. What is the best remedy?#
Options
Show answer
Shard on a high-cardinality key, such as a hash of the tenant or entity id, so writes spread evenly across partitions. A monotonic timestamp routes every current write to whichever shard owns the latest range, creating a hot partition no matter how many shards exist. Adding replicas or vertically scaling the busy shard only raises one box's ceiling; neither balances the write distribution.
A monotonic timestamp routes all current writes to whichever shard owns the latest range, creating a hot partition no matter how many shards exist. Choosing a high-cardinality key (a hash of tenant or entity id) distributes writes uniformly, which is the actual fix. Adding replicas helps read load but not the write hotspot — replicas still funnel writes through one leader. Raising the shard count without changing the key leaves the newest range concentrated on a single shard. Vertically scaling the hot shard only raises its ceiling and reintroduces a single point of contention; it does not balance the distribution.
System Design/sd-data/id-generation
You're sharding a table across many database nodes and need globally unique primary keys generated at high throughput without a per-insert coordination round-trip, and you'd like them to be roughly time-sortable for index locality. Which approach best fits?#
Options
Show answer
Use Snowflake-style ids that pack a timestamp, a node id, and a per-node sequence counter — coordination-free, unique, and naturally time-ordered. Every node mints ids locally with no round-trip, and the timestamp prefix gives the index locality you want. A central sequence reintroduces the coordination and single point of failure; UUIDv4 is coordination-free but fully random, so it scatters B-tree inserts; and hashing row contents lets identical rows collide.
Snowflake-style ids compose a millisecond timestamp + a per-node id + a per-node monotonic counter, so every node mints unique, roughly time-sortable ids locally with no coordination — exactly the stated requirements. A central sequence (a) is unique but reintroduces the coordination round-trip and a SPOF the question rules out. UUIDv4 (b) is genuinely coordination-free and a valid choice, but its full randomness defeats the time-sortability/index-locality goal — that's the honest trade that makes it second-best here. Hashing row contents (d) is broken for keys: two rows with identical values collide, so it cannot guarantee uniqueness.
System Design/sd-data/resharding
One partition (key range) in your sharded cluster has become a hotspot, and you must rebalance its load across more nodes while serving live traffic. Which approaches genuinely help redistribute the hot partition with minimal disruption?#
Options
Pick every one that applies.
Show answer
The approaches that genuinely help are splitting the hot key range into smaller sub-ranges and migrating some while double-writing during cutover, using consistent hashing with virtual nodes so adding capacity remaps only a fraction of keys, and introducing a finer-grained or composite shard key so the concentrated key spreads across partitions. Taking the shard offline to dump and restore is downtime, and vertically scaling only the hot node never rebalances anything.
Online resharding is about moving load without a stop-the-world cutover. Range splitting with a double-write window (a) lets you migrate a sub-range while both locations stay readable, then flip reads once backfill completes — the standard live-migration pattern. Consistent hashing with virtual nodes (b) bounds the blast radius of adding a node to ~1/N of keys instead of a near-total remap, which is what makes scaling out incremental. A finer/composite shard key (d) attacks the root cause when the heat comes from a concentrated key, fanning it across partitions. The wrong options fail the 'live traffic, minimal disruption' constraint: dumping and restoring offline (c) is downtime, not minimal disruption; and vertically scaling only the hot node (e) just raises one box's ceiling without rebalancing — the partition stays a single hotspot and a SPOF.
System Design/sd-data/sharding
How would you choose a shard key, and what goes wrong with a bad one?#
Show answer
A good shard key has high cardinality and a roughly uniform access distribution so reads and writes spread evenly across shards, and it ideally matches your most common query so requests hit a single shard instead of scattering. A bad shard key creates a hotspot: a low-cardinality or monotonically increasing key (like an auto-increment id or a timestamp) sends most traffic to one shard, producing uneven load and a hot partition while others sit idle. It also forces expensive scatter-gather queries and painful resharding when one shard outgrows its capacity.
The shard key decides how rows map to partitions, so it governs load balance and query routing. Aim for high cardinality, even distribution, and alignment with your dominant access pattern (so common queries are single-shard). The classic mistake is a monotonic or low-cardinality key, which concentrates traffic into a hotspot, forces scatter-gather across all shards, and makes rebalancing expensive — composite or hash-based keys often mitigate this.
System Design/sd-data/id-generation
Design a URL shortener (think Bitly / TinyURL).#
Show answer
Requirements. Two operations dominate: create (write a mapping, optionally with a custom alias and expiry) and redirect (look up a code and 30x to the long URL). The system is overwhelmingly read-heavy (100:1), so the redirect path is what we optimise. I'd confirm: are custom aliases required (yes), do links expire (optional TTL), and how precise must click counts be (approximate/eventually-consistent is fine). I'd use a 302 (temporary) redirect so we keep serving redirects through our system and can still count clicks; a 301 would let browsers cache and bypass us.
Capacity. ~40 writes/sec average, ~200/sec peak; ~4K reads/sec average, ~20K peak. Storage: ~6B links over 5 years; at ~500 bytes/row (code, URL, metadata) that's ~3 TB — comfortably shardable. Keyspace: base62 with 7 characters gives 62⁷ ≈ 3.5×10¹² codes, far more than 6B, so 7 chars (often padded to a fixed length) is plenty. The hot working set (recently/ popularly accessed links) is a small fraction of 6B, so a cache of tens of GB covers the bulk of redirect traffic.
Data model. A single mapping table/collection keyed by the short code: code (PK) → long_url, created_at, expires_at, owner_id. Point lookups by primary key suit a key-value store (DynamoDB/Redis-backed) or a sharded relational table sharded by code. Click counts live separately — incrementing a counter on every redirect would put write load on the read path — so clicks are emitted as events and aggregated asynchronously.
Short-code generation. Generate a unique 64-bit id (a distributed counter handed out in ranges per server, or a Snowflake-style id) and base62-encode it to get the short code; this guarantees uniqueness with no collision checks and no hotspot from a single shared counter. Custom aliases are written directly with a uniqueness check and stored in the same table, reserving that code. (A hash-of-URL scheme is the alternative but needs collision handling and breaks idempotency for duplicate URLs.)
Read/write paths. Write: allocate id → base62 → insert mapping → return the short URL. Redirect: a load balancer / CDN fronts stateless app servers; the server does a cache-aside lookup (code → long_url) in Redis, falling back to the store on a miss and populating the cache, then returns a 302. This keeps p99 well under 100 ms for cache hits. Expiry is enforced by TTL on both the row and the cache entry. Each redirect fires a lightweight click event onto a queue (Kafka) that a consumer aggregates into per-link counts.
Bottleneck & scaling. The bottleneck is the redirect read path at peak. We scale it with cache layers (most reads never touch the store), read replicas, and sharding the mapping store by code so lookups stay single-shard. Id generation scales by handing each server an independent id range (or using Snowflake), avoiding a single global counter as a SPOF. At 10× traffic, the cache absorbs most of it; we add cache nodes and replicas and, if needed, push redirects further to the edge.
A URL shortener is the canonical warm-up design: it forces a clean separation of a heavily read-optimised redirect path from a comparatively rare write path. The two pivotal decisions are short-code generation (a base62-encoded distributed id avoids both collisions and the hotspot of a single shared counter) and caching (cache-aside on code→URL, fronted by a CDN/LB, is what meets the latency budget when reads outnumber writes 100:1). Click counting is deliberately moved off the redirect path and made asynchronous so analytics never slow a redirect. The dominant bottleneck is the redirect read path, scaled by caching, read replicas, and sharding the mapping store by code.
System Design/sd-data/sharding
Sharding a database by a high-cardinality monotonically increasing key (e.g. auto-increment ID or timestamp) guarantees even write distribution across shards.#
Options
Show answer
False. Monotonically increasing keys concentrate all writes on the last shard — the one owning the current maximum range — the classic hotspot-shard problem, while other shards sit idle for writes. Even though such keys are high-cardinality, they are temporally correlated. Even write distribution needs a hash-based or consistent-hash key, or random keys like UUIDv4; timestamp-ordered ULIDs recreate the same hotspot.
Monotonically increasing keys concentrate all writes on the last shard — the one that owns the current maximum range. This is the classic hotspot shard problem: every new row lands on one node while all other shards sit idle for writes. The correct sharding strategy for write-heavy workloads is a hash-based or consistent-hash key that scatters new rows uniformly, at the cost of losing range-scan locality. Alternatively, prefix the key with a random bucket, or use random keys like UUIDv4 that have no temporal correlation. Note that timestamp-prefixed IDs such as ULIDs do not help here — because they are monotonically increasing they recreate the same last-shard hotspot.
System Design/sd-data/sharding
What problem does consistent hashing solve when sharding data across nodes, and how do virtual nodes help?#
Show answer
Naive sharding with hash(key) % N remaps almost every key when N changes, forcing a massive reshuffle whenever you add or remove a node. Consistent hashing places nodes and keys on a hash ring so that adding/removing a node only moves the keys in one adjacent arc — roughly 1/N of the data — rather than nearly all of it. Virtual nodes (many ring positions per physical node) smooth out the uneven key distribution and let you weight heterogeneous machines, avoiding hot shards.
Consistent hashing is foundational to distributed caches and databases (Dynamo, Cassandra, memcached clients) precisely because it minimises rebalancing churn during scaling and failure events. Without virtual nodes a small ring produces lopsided load; with them, distribution converges to near-uniform.
System Design/sd-patterns/message-queues
Design a real-time chat system (think WhatsApp / Messenger) supporting 1:1 and group conversations, with online/offline delivery and read receipts.#
Show answer
Requirements. Two delivery modes dominate: online (push immediately over a live connection) and offline (store and deliver on reconnect). Groups are bounded (~256), so a message fans out to at most a few hundred recipients. I'd confirm ordering is per-conversation (not global), and that 'exactly once' really means at-least-once delivery plus client dedup by message id. Presence and receipts are separate, lighter sub-systems.
Connection layer. Clients hold a WebSocket to a stateless gateway server (one of thousands; at ~500K connections each, ~1,000 servers cover the peak). The hard part is routing: when A sends to B, A's gateway must find B's gateway. A connection registry (user_id → gateway_id, in Redis) updated on connect/disconnect, or a pub/sub channel per user that B's gateway subscribes to, does this. The sender's gateway looks up B, forwards the message to B's gateway, which pushes it down B's socket.
Data model. Messages are stored keyed by conversation_id and ordered by a time-sortable id, in a write-optimised, horizontally sharded store (Cassandra/HBase-style), sharded by conversation. For offline delivery each recipient has a mailbox/inbox (a queue of undelivered message ids); on reconnect the client drains it. Recent history is hot (cached / on fast storage); old history ages into cold archive.
Send → deliver flow. Send → server assigns a message id and persists to the conversation log → for each recipient, if online, push to their gateway; if offline, enqueue in their mailbox. Delivery is at-least-once; the client deduplicates by message id, giving exactly-once feel. Delivery and read receipts are just small messages flowing the other way, updating per-message state. The sender never blocks on fan-out — it returns once the message is durably persisted.
Storage & fan-out scaling. ~15 TB/day shards cleanly by conversation; group fan-out (1 send → up to 256 deliveries) is done asynchronously by workers reading the conversation log, so a large group never stalls the sender. Hot recent messages live in cache; archives compress to cheap storage.
Presence & failure. Presence is a heartbeat with a short TTL in an in-memory store — the client refreshes every ~30 s, and absence of a refresh means offline; we never write every status flip to a database. If a gateway crashes, its ~500K connections drop, clients reconnect to another gateway (the registry updates), and any messages sent meanwhile are already durably in each recipient's mailbox, so nothing is lost — they arrive on reconnect.
A chat system tests whether you can reason about stateful persistent connections at scale. The defining challenge is routing: with hundreds of millions of live WebSocket connections spread over thousands of stateless gateways, sending a message means finding the gateway that holds the recipient's connection — solved with a connection registry or per-user pub/sub. Durability comes from persisting every message to a conversation log plus a per-user mailbox, so offline users and gateway crashes never lose messages (at-least-once delivery + client-side dedup by message id gives exactly-once feel). Presence is deliberately made cheap with short-TTL heartbeats rather than database writes, and group fan-out is pushed off the sender's critical path.
System Design/sd-fundamentals/caching-strategies
Design a social news feed (home timeline, à la Twitter/Facebook): each user opens the app and sees a feed of recent posts from the accounts they follow.#
Show answer
Requirements. The workload is ~20:1 read-heavy, so the read (feed assembly) path is what we optimise. Ordering can be reverse-chronological to start (ranking is an orthogonal layer). The defining edge case is the follower skew: a normal post reaches ~200 timelines, a celebrity post reaches up to 100M — those need different handling.
Fan-out strategy. The core decision. Fan-out-on-write (push each post id into every follower's precomputed timeline) makes reads cheap but explodes on celebrities — one post = 100M writes. Fan-out-on-read (assemble at query time by pulling recent posts from everyone you follow) makes celebrity posts free but makes every feed read expensive. I'd use a hybrid: push on write for ordinary accounts, and for celebrities skip the push and pull their recent posts at read time, merging them into the timeline. This caps write amplification while keeping normal reads cheap.
Feed storage. Each user has a precomputed timeline: a capped list of recent post ids (say the latest few hundred) in Redis. Post bodies live once in a separate post store (sharded by post id). Storing ids — not full copies — per follower keeps the timeline small and means an edited/deleted post is resolved at hydration time.
Read path. On refresh: read the user's precomputed timeline (ids from pushed accounts) → pull recent post ids from the handful of celebrities they follow → merge and sort by time → hydrate the top N bodies from the post cache → return. Almost everything is served from cache, so p99 stays under 200 ms. The celebrity pull is bounded because a user follows only a few of them.
Bottleneck & scaling. The bottleneck is fan-out write amplification: 100M posts/day × ~200 avg followers ≈ tens of billions of timeline writes/day, and the tail (celebrities) is what kills pure push. The hybrid split removes the worst offenders; the remaining fan-out runs on async workers off a queue so posting never blocks; timelines and post bodies are cached and sharded. At higher load we add fan-out workers and cache capacity, and tune the push/pull follower threshold.
The news feed is the canonical fan-out problem, and the whole interview hinges on one decision: fan-out-on-write vs fan-out-on-read. Push (write) makes reads trivial but suffers catastrophic write amplification for high-follower accounts — one celebrity post would mean 100M timeline writes. Pull (read) makes posting cheap but every feed assembly becomes expensive. The correct answer is a hybrid: push for ordinary accounts, pull-and-merge for celebrities, which bounds the worst case on both sides. Storing post ids (not full copies) in per-user timelines, hydrating bodies from a shared cache, and running fan-out on async workers off the posting path are the supporting moves that keep the read path inside the latency budget.
System Design/sd-data/cap-consistency
Design a file storage & sync service (think Dropbox / Google Drive): users store files in the cloud and keep them in sync across multiple devices.#
Show answer
Requirements. Beyond upload/download, the product is sync: a change on one device must propagate to the others, efficiently. 'Efficiently' means delta sync — never re-send an unchanged file. We must handle conflicts (two offline edits) without data loss and keep version history. Metadata ops (browse folders) are latency-sensitive; bulk byte transfer is throughput-bound.
Chunking & dedup. Files are split into content-hashed chunks (~4 MB; a chunk's id is the hash of its bytes). Editing one paragraph of a 1 GB file changes only the chunk(s) covering it, so the client uploads just those — a few MB, not a GB. Because chunks are content-addressed, identical chunks (same file shared by many users, or unchanged blocks across versions) are stored once — large dedup savings.
Block vs metadata split. Chunk bytes go to an object store (S3-style): content-addressed, effectively infinite, optimised for large immutable blobs — this holds the exabytes. The metadata DB holds the file→chunk-list mapping, the folder tree, ownership, and version history — small records with fast point/range reads to meet the <100 ms browse budget. The two layers have opposite access patterns, so they're separate systems.
Sync protocol & conflicts. Each device keeps a local view and learns of remote changes via a notification service (long-poll/push): 'your namespace changed past version V'. The client then pulls the metadata diff since its last-known version and downloads only the new chunks. Concurrent offline edits are detected by version: the second writer's base version is stale, so rather than overwrite, the system keeps both as a conflict copy (or merges where it safely can) and surfaces it — never a silent loss.
Consistency. Per file/namespace there's a monotonic version; metadata operations are ordered by it so all devices converge on the same state. Upload is commit-after-chunks: the client first writes all chunks to the object store, then flips the metadata pointer to the new chunk list. A half-uploaded file's pointer never goes live, so a reader never sees a file referencing chunks that aren't fully stored.
Scaling. The metadata DB shards by user_id (a user's files and folder tree stay together). The object store scales horizontally on its own. Sync notifications fan out to each of a user's devices. Dedup + delta sync are the multipliers — they cut both stored bytes and transferred bytes by large factors, which is what makes exabyte-scale storage and 5,800 ops/sec affordable.
A file sync service is defined by two structural decisions. First, content-addressed chunking: splitting files into ~4 MB hash-named blocks gives you delta sync (edit one paragraph of a 1 GB file → only the changed chunk transfers) and deduplication (identical chunks stored once) for free. Second, the block/metadata split: chunk bytes live in an object store built for huge immutable blobs and exabyte scale, while the file→chunk mapping and folder tree live in a separate, fast metadata DB — they have opposite access patterns. Correctness rests on commit-after-chunks ordering (flip the metadata pointer only after all chunks are durably stored) and version-based conflict handling that keeps a conflict copy rather than silently dropping one of two concurrent offline edits.
Related interview questions
Job market
See system-design salaries and hiring demand from live job postings.
The other 2 questions
This page shows 10 and marks what you pick. That's as far as a page can go. A free account opens the other 2 and keeps every answer. What you miss comes back until it's right: after a day, then at longer gaps.
Free · the whole bank · 100 marked answers per 30 days · written feedback on the paid plan