Sharding splits a collection across several replica sets (shards). Each document lives on exactly one shard, decided by its shard key value. The mongos routers use the key to send each query to the shards that hold relevant data. Pick the key well and both writes and reads spread out. Pick it badly and you’ve paid for ten shards while one does all the work. That’s why this is one of the most common senior MongoDB interview topics.
Commands are mongosh for MongoDB 7 and 8, and outputs are illustrative; no sharded cluster was available while writing.
How the key is used
Data is divided into chunks, each covering a contiguous range of shard key values (or of hashed values, for a hashed key). The balancer moves chunks between shards to keep data volume even. When a query includes the shard key (equality or a range), mongos can target just the shards whose chunks overlap. When it doesn’t, mongos sends the query to every shard and merges results: a scatter-gather query.
sh.shardCollection("shop.orders", { customerId: 1, orderDate: 1 })The four properties to check
Cardinality. How many distinct values can the key have? A key with few values limits how many chunks you can ever have. Sharding on country caps you at about 200 chunks, and if most customers are in one country, that chunk can’t be split. It becomes a jumbo chunk that the balancer can’t move.
Frequency. Even with many distinct values, if one value is extremely common (a tenantId where one tenant is 40% of the data), its documents all share one key value and can’t be split across shards.
Monotonic change. If the key always increases, like a timestamp or a default ObjectId, every new document has the highest key so far, so every insert goes to the chunk covering “max”, on one shard. That shard takes the entire write load while others sit idle, and the balancer is constantly moving chunks off it.
Query isolation. Do your common queries include the key? If 90% of reads are “orders for customer X”, a key starting with customerId makes those reads hit one shard. If the key is something queries never filter on, every read is scatter-gather, which gets slower as you add shards.
Working through candidates
An orders collection, 2 billion documents, 50,000 inserts per second at peak. Main queries: a customer’s order history, and a single order by orderId.
{ orderDate: 1 } (ranged). Monotonic: all inserts land on one shard. Customer history queries don’t include it. Rejected.
{ _id: "hashed" }. Writes spread perfectly. But customer history queries don’t include _id, so they’re scatter-gather across every shard. Single-order lookups by _id are targeted. Workable but poor for the main read.
{ customerId: 1 } (ranged). High cardinality (millions of customers), inserts spread across customers, customer history goes to one shard. But a few huge business customers might each have millions of orders, all under one key value, creating jumbo chunks.
{ customerId: 1, orderDate: 1 } (ranged, compound). Customer history is targeted, and a huge customer’s orders can be split by date into several chunks, so no jumbo chunks. Inserts are spread by customer. Within one customer, dates increase, but that’s spread across millions of customers, so no single hot spot. This is the strongest candidate.
{ customerId: "hashed" }. Spreads writes and targets equality lookups on customerId, but range queries on customerId become scatter-gather (rarely needed here). You can’t split one customer’s orders, so the big-customer problem returns.
Single-order lookups by orderId don’t include the key in any of these options. Either include customerId in the API (the order URL often has it) or accept scatter-gather for that lookup, which is fine if it’s not the hot path.
Hashed versus ranged in one paragraph
Hashed keys hash the value and range-partition the hashes, so consecutive values scatter. That fixes monotonic inserts, at the cost of losing range targeting: orderDate > X on a hashed orderDate hits every shard. Ranged keys keep neighbouring values together, which makes range queries efficient but leaves you exposed to hot spots. Compound keys like { customerId: 1, orderDate: 1 }, or a hashed prefix with a ranged suffix ({ region: "hashed", ts: 1 } is allowed since 4.4), let you get some of both.
Uniqueness and other constraints
- A unique index on a sharded collection must be prefixed by the shard key, because each shard can only enforce uniqueness on its own data. If
emailmust be globally unique but the shard key istenantId, you need a different approach (a separate unsharded lookup collection, for instance). - Since 4.2 you can update a document’s shard key value (it may move shards), but the update must include the full shard key in the filter, typically inside a transaction or as a retryable write.
- Every document must have the shard key fields. Since 4.4, missing fields are treated as null, which tends to create one large null chunk if a writer forgets them.
When the key is wrong
You’re no longer stuck with a bad key forever:
refineCollectionShardKey(4.4+) appends fields to an existing key, for example from{ customerId: 1 }to{ customerId: 1, orderDate: 1 }, to split jumbo chunks. It’s cheap; it doesn’t move data immediately.reshardCollection(5.0+) changes the key completely by building a new copy of the collection under the new key while writes continue, then cutting over. It needs time, disk space (roughly 1.2 times the collection on each shard per the docs’ guidance) and spare I/O. Plan it like a migration.- MongoDB 8.0 adds
unshardCollectionandmoveCollectionfor collections that turned out not to need sharding.
The fact that it’s fixable doesn’t make the choice unimportant. A 2-billion-document reshard is a project, not a command.
Interview practice
An IoT platform shards readings on { deviceId: 1, ts: 1 }. There are 200,000 devices. Writes spread nicely, but the dashboard query “average temperature across all devices in the last 5 minutes” is slow and gets slower as shards are added. A teammate proposes resharding on { ts: 1 }. Evaluate that.
Answer
Resharding on { ts: 1 } would make that dashboard query targeted, since the last 5 minutes would sit in one or two chunks. But it would also send every insert to the shard holding the newest chunk, because ts increases monotonically. With 200,000 devices writing constantly, that’s the worst possible write pattern, and write throughput would collapse to one shard’s capacity. A hashed ts would fix the writes but turn the 5-minute range query back into scatter-gather, which is where we started.
The current key is right for the dominant workload (lots of writes, per-device queries). The dashboard query is an aggregate across everything, and scatter-gather is the natural shape for it: each shard computes a partial average over its own recent readings in parallel. If it’s slow, I’d look at whether each shard has an index on { ts: 1 } (the shard key index leads with deviceId, so it doesn’t help a pure time range), and I’d consider precomputing: a small minute_stats collection updated by the ingest path or a periodic job, which the dashboard reads directly. Alternatively, a time-series collection with deviceId as the metaField would bucket readings and make this kind of aggregation much cheaper.
Further reading
- MongoDB interview questions
- MongoDB MCQs
- MongoDB ObjectId and Identifier Semantics
- MongoDB Time Series Collections
- Official docs: Choose a shard key