Handling Concurrency in Product Requirements
What happens when two users edit the same record at the same time? If your spec doesn't answer that question, the engineer holding the keyboard will — silently, without review, and probably wrong. Product requirements are written from the perspective of one user doing one thing, which means concurrent scenarios almost never appear in a spec until they appear as a production bug.
Why concurrency rarely appears in product specs
The reason is simple: we think in stories, and stories have one protagonist. A form is submitted. A button is clicked. A record is updated. Concurrency bugs come from the scenarios where there are two protagonists — two users, two processes, two browser tabs — acting on the same data at the same time.
These bugs are among the hardest to catch in testing because they require exact timing to trigger. They show up in production under real load, at exactly the worst moments. After the CRM incident, I started adding a "concurrent scenarios" section to every spec for features that touch shared state. It's caught problems in about 40% of the specs I've reviewed since — which tells me 40% of our features would have shipped with undocumented concurrency behavior.
Identifying concurrent scenarios during spec writing
For any feature that reads and then writes a shared record, ask these questions while writing the spec:
- What happens if two users edit this record simultaneously?
- What happens if the user submits this form twice before the first response arrives?
- What happens if two background jobs process the same record at the same time?
- What happens if a user triggers this action from two browser tabs?
- What happens if a webhook fires while the user is also editing the record it affects?
Spec Without Concurrency
Given a user on /contacts/:id/edit When they update the phone number Then the contact is saved
Spec With Concurrency
Given two users editing /contacts/:id When both save within 5 seconds Then the second save returns 409 Conflict with the first user's changes shown
If any of those questions produces an answer that involves "last write wins" without intending that, or "it depends" without a policy, the spec has a concurrency gap.
Documenting the double-submit problem
The most common concurrency issue in web applications: a user clicks a button, the response is slow, and they click again. Without explicit handling, this creates duplicate orders, duplicate payments, or duplicate records. The spec should define the behavior explicitly.
Concurrency requirement — Order placement:
Problem: User clicks "Place Order" while first request is still in flight.
Required behavior:
- Client must send X-Idempotency-Key: [UUID] header on all order creation requests.
- If a request arrives with an idempotency key that matches a pending or completed
order from the same session, return HTTP 200 with the existing order — do not create a new one.
- If the key is absent, return HTTP 422 with error: "idempotency_key_required".
- Idempotency keys expire after 24 hours.
QA verification:
- Submit two identical requests with the same idempotency key within 500ms.
- Expected: one order created, second returns 200 with existing order ID.
Optimistic locking in the spec
When two users edit the same record and one of them would silently overwrite the other's changes, that is a lost update. The spec should define whether this is acceptable (last write wins) or must be prevented (optimistic locking required). This is a product decision, not an engineering decision. The engineer cannot make it for you.
Concurrency requirement — Contact record editing:
Strategy: optimistic locking via version field.
Behavior:
- GET /contacts/:id response includes "version": 42
- PUT /contacts/:id request must include "version": 42
- If the version in the request does not match the current DB version:
return HTTP 409 Conflict with:
{ "error": "version_conflict", "current_version": 43 }
- Client must refresh the record and re-apply their changes before retrying.
- No silent overwrites. Ever.
User-facing message on conflict:
"Another user has made changes to this contact. Please review and resubmit."
Race conditions in state machines
Workflow features — orders, applications, approval chains — are especially vulnerable to concurrent state transitions. Two operations that are each individually valid can produce an invalid state if they race. The spec must name which transitions are mutually exclusive and how conflicts are handled.
Concurrency requirement — Invoice approval workflow:
Problem: Two approvers click "Approve" simultaneously. Invoice must not be
approved twice or transition to a broken intermediate state.
Required behavior:
- APPROVE operation uses a database-level row lock on the invoice record.
- First APPROVE succeeds: invoice moves to APPROVED, notification fires.
- Second APPROVE arrives while lock is held: returns HTTP 409 with current state.
- If both arrive within the same millisecond: database serializes them; outcome is
one approval with a single state transition — no duplicate events.
Test case: fire two concurrent APPROVE requests via parallel API calls.
Expected: one 200, one 409. Invoice state = APPROVED. Exactly one approval event in audit log.
Idempotency requirements for background jobs
Background jobs — event processors, webhook handlers, scheduled tasks — can be retried by infrastructure automatically. If the job is not idempotent, a retry creates a duplicate. The spec should state whether idempotency is required and how it is implemented.
Idempotency requirement — Payment webhook handler:
Problem: Payment gateway may deliver the same webhook event more than once.
Required behavior:
- Store processed event IDs in the webhook_events table with a UNIQUE constraint
on (gateway, event_id).
- Before processing: insert with ON CONFLICT DO NOTHING.
- If insert is a no-op (duplicate): return 200 to gateway, take no action.
- Processing must be wrapped in a transaction: either the record is created
AND the payment is marked as paid, or neither happens.
Anti-pattern to avoid: checking "has this been processed?" and then processing
in two separate operations — a retry between those two steps causes a duplicate.
The "two users edit the same record" scenario
For any collaborative or multi-user feature, the spec must answer: what does the second user see when they try to save a record that has been modified since they opened it? The three options are last-write-wins (simple, lossy), conflict detection with error (safe, requires client handling), and merge (complex, rarely worth it for most features). Pick one, document it, and make sure the frontend knows which behavior to implement.
Specifying concurrency behavior QA can test
Concurrency requirements must be written at the level of testable assertions, not architectural descriptions. "Uses optimistic locking" is an implementation note. "Returns HTTP 409 when version field does not match current record" is a testable requirement. QA can write a test for the latter without reading any code.
For each concurrent scenario in the spec, write the trigger (how to produce it), the expected HTTP response or state outcome, and the observable side effects (audit log entries, notification sends, database state). This is the difference between a concurrency spec that prevents bugs and one that just names the problem without solving it.
The concurrency checklist for spec review
Before approving any spec that involves shared mutable state, confirm these cases are addressed:
- Double-submit: what happens if the same request is sent twice? Is idempotency required?
- Concurrent edits: is last-write-wins acceptable, or is optimistic locking required?
- State machine races: are there transitions that must be mutually exclusive?
- Background job retries: are jobs idempotent? What is the deduplication mechanism?
- Webhook or event replay: can the system receive the same event more than once without side effects?
Not every feature needs all five. But for anything that modifies shared records, processes payments, or drives a workflow, every one of these must be a conscious decision — not something an engineer decides at 10pm during implementation without any record of the choice being made.
Keep reading
Editorial note
This article covers Handling Concurrency in Product Requirements for software delivery teams. Examples are illustrative engineering scenarios, not legal, tax, or investment advice.
- Author details: Daniel Marsh
- Editorial policy: How we review and update articles
- Corrections: Contact the editor