Concurrency and race condition review
Assume two requests land at the same moment, then walk the shared state item by item
Review the code below for concurrency safety. Method: assume two requests arrive in the same millisecond and walk every piece of shared state. Please: 1) list all shared mutable state (in-memory variables, caches, database rows, files); 2) point out where there is a gap between checking and using (read-then-write); 3) say at what concurrency it surfaces, and what it looks like when it does; 4) give the minimal fix (lock, unique constraint, atomic operation, make it idempotent) and the cost of each. ``` [paste your code] ```
How to use
Worth running even on a low-traffic project — a read-then-write hole usually only blows up once the traffic shows up.
Good for
- There is check-then-write logic: stock decrement, coupon claim, idempotent order creation.
- Single-node tests always pass, but duplicates start appearing after multi-instance deployment or a load test.
Not for
- Single process, no shared state, pure functions: there is nothing to review.
- You already know it is isolation level or lock waits: read the locks and transactions, not the whole file.
Example
Input
def claim_coupon(user_id, coupon_id):
if db.exists("select 1 from claims where user_id=? and coupon_id=?", user_id, coupon_id):
return "already"
if db.count("select count(*) from claims where coupon_id=?", coupon_id) >= 100:
return "sold_out"
db.insert("claims", user_id=user_id, coupon_id=coupon_id)
return "ok"What you get
The shared mutable state is the claims table and the stock count; there are two check-then-act gaps — two concurrent requests from the same user both pass exists, and at 99 claims both requests pass count<100. It surfaces with two instances at roughly 10 QPS during a coupon rush, as duplicate claims and overselling. Minimal fix: a unique constraint on (user_id, coupon_id) as the backstop plus a single atomic `INSERT ... SELECT ... WHERE count < 100`, at the cost of retrying on conflict.
Common mistakes
- Saying only 'check for concurrency issues' without the deployment shape (how many instances, Redis lock or not) gets single-node-safe code flagged as dangerous.
- Taking 'add a lock' literally and adding an in-process lock, which does nothing across instances.
Try tweaking it like this
- Add 'assume 3 instances and no shared cache' before it counts in-process variables as shared state.
- Ask for 'the outline of a load test that reliably reproduces it (concurrency, request order)' to turn the risk into an experiment.