When your app sends a write to MongoDB, it waits for an acknowledgement. Write concern sets the bar for that acknowledgement: how many replica set members must have the write, and whether it must be in the on-disk journal. It doesn’t change what’s written; it changes what “success” means when your code gets it. Interview questions focus on what each level protects against and what a timeout tells you.
Snippets are mongosh and Node.js driver style for MongoDB 7 and 8, and are illustrative; no replica set was available while writing.
The three parts
db.orders.insertOne(
{ _id: 9001, total: 120 },
{ writeConcern: { w: "majority", j: true, wtimeout: 5000 } }
)w: how many members must acknowledge.
w: 0: don’t wait at all. Fire and forget. You won’t even learn about errors like duplicate keys.w: 1: the primary has applied it.w: "majority": a majority of voting, data-bearing members have it.w: <n>or a custom tag set: a specific count, or members with certain tags (for example, “one in each data centre”).
j: whether the write must be in the on-disk journal before acknowledging. With j: true, a crash right after acknowledgement doesn’t lose it on the acknowledging members. With w: "majority", journaling is implied by default (writeConcernMajorityJournalDefault is true), so on most deployments j matters only for w: 1.
wtimeout: how long to wait for the w requirement, in milliseconds. Without it, a majority write can wait indefinitely if a majority isn’t reachable.
What each level actually protects
Walk through a failure. A three-member set; the app inserts an order.
With w: 1. The primary applies the insert and replies “ok”. A second later, before the secondaries copy it, the primary’s host dies. A secondary is elected, without the order. When the old primary returns, it rolls the order back into a rollback file. The app told the customer “order placed”, and the order doesn’t exist in the database.
With w: "majority". The primary replies only after at least one secondary also has the insert (two of three). If the primary now dies, the elected primary must be at least as up to date as a majority, so it has the order. The acknowledgement is durable across failover.
That’s the real distinction: majority means an acknowledged write won’t be rolled back. It’s not about having extra copies for their own sake.
w: 0 and w: 1 are tempting for speed, but the latency difference is usually one network round trip to the nearest secondary, often a millisecond or two within a region. Cross-region members add more, which is a reason to think about member placement, not a reason to drop to w: 1 for important data.
Defaults and where to set them
Since MongoDB 5.0, the implicit default write concern is w: "majority" for most replica sets and sharded clusters. The exception is a primary-secondary-arbiter (PSA) arrangement, where the default becomes w: 1 because with one data-bearing member down a majority write couldn’t be satisfied. You can see and set the cluster-wide default:
db.adminCommand({ getDefaultRWConcern: 1 })
db.adminCommand({ setDefaultRWConcern: 1, defaultWriteConcern: { w: "majority" } })You can also set it in the connection string (?w=majority), on the client, database or collection object, or per operation. A sensible pattern is a strong default with deliberate per-operation weakening for low-value, high-volume writes such as click logging or metrics, where losing a few seconds of data in a rare failover is acceptable.
Inside a transaction, write concern is set on the transaction, not on individual operations.
Reading a wtimeout error
MongoWriteConcernError: waiting for replication timed out
code: 64, codeName: 'WriteConcernFailed', errInfo: { wtimeout: true }This is the part interviewers like most. A write concern timeout does not mean the write failed. The primary applied it. The secondaries may apply it a second later. The error only says “I couldn’t confirm the level you asked for within 5 seconds”.
So the app is in an uncertain state. Blindly retrying a non-idempotent write (an insert with a server-generated id, an $inc) can apply it twice. The safe patterns:
- Make the write idempotent: insert with a client-chosen
_idor an idempotency key under a unique index, use$setto fixed values rather than$incwhere possible. - After a timeout, read back (with
readConcern: "majority"once the set is healthy) to find out what happened. - Alert on write concern timeouts; they usually mean a secondary is down or lagging badly.
Note that retryable writes in the driver cover network errors and “not primary” errors, and the driver also retries certain write concern errors that come from a failover. A plain wtimeout isn’t one of the cases the driver retries for you.
Write concern and reads go together
Write concern defines what’s durable; read concern defines what you’re allowed to see. A w: "majority" write followed by a readConcern: "local" read from the primary sees the write, but so would a w: 1 write. To make sure a reader never sees data that might be rolled back, use readConcern: "majority". And to read your own majority writes on a secondary, use a causally consistent session. Mentioning this pairing shows you understand the whole picture rather than one setting.
Interview practice
An analytics team wants to speed up event ingestion (80,000 inserts per second) by switching from the default to { w: 0 }. The same cluster also stores user account changes. What do you advise?
Answer
First, separate the two workloads. Write concern can be set per operation or per collection handle, so the event ingestion path can be relaxed without touching account writes, which should stay at w: "majority" because a rolled-back password change or email update is a real incident.
For events, I’d push back on w: 0. It doesn’t just risk rollback in a failover; it hides all errors, including validation failures, duplicate keys and a full disk. You’d lose data silently even when nothing fails over. w: 1 keeps error reporting and costs very little more. If throughput is the issue, larger batched writes (insertMany with ordered: false, or bulkWrite) give a far bigger gain than lowering the write concern, because they cut round trips.
I’d also ask what losing events means for them. If a few seconds of events lost during a rare failover is acceptable, w: 1 with batching is a sound choice. If events drive billing, keep majority and scale with batching and sharding instead. Finally, whatever they choose, make inserts idempotent with a client-generated event id as _id, so retries after timeouts or network errors don’t double count.