Quick answer: Concurrent bugs depend on timing, so ordinary tests see them at random and a green run proves little. Make the interleaving deterministic by releasing every request from one start barrier and using fixed delays to control completion order, then assert on the final state, looking for lost updates, duplicate side effects, and stale results from out-of-order responses. If the same test passes or fails without a code change, the test has not yet pinned down the race.

Why concurrency slips past tests

A loop that fires several requests is not truly concurrent, because each call starts a few instructions apart and the scheduler decides the rest. The failure only appears under one particular ordering, which means the bug hides on most runs and shows up in production under load. Two things need coverage: how the client handles overlapping calls, and how the shared resource behaves when more than one request touches it. On the client side, responses can resolve in a different order than the requests were sent, and shared in-memory state can be updated by callbacks that no longer reflect the latest call. On the resource side, read-modify-write and check-then-act sequences can interleave so the last writer quietly undoes the first.

Make the interleaving deterministic

Use a start barrier, a latch or a promise gate, so every request is built first and released at the same instant instead of trickling out of a loop. Then use fixed, known delays rather than real network timing to choose which response lands first. Hold one request at a synchronization point while the other runs to reproduce a specific crossing, and run both orderings, A first and B first, as separate cases. Assert on the resulting state rather than on timing, and keep the delay values large enough that a slow machine cannot blur them. A desktop API workspace lets you drive these calls against a local mock with fixed latency and conditional responses, so the interleaving is chosen in advance and the same run reproduces every time. Powerduck works this way, keeping the concurrency test local and repeatable rather than dependent on live scheduling.

The race patterns worth pinning down

Race pattern Interleaving Result without a guard Typical guard
Lost update Both read, then both write Last write wins, first update lost Version or ETag, atomic update
Check-then-act Both pass the check, both act Duplicate side effect Unique constraint, lock
One-time initialization Both see it missing, both initialize Double setup Once primitive, atomic create
Out-of-order completion B resolves before A Stale state shown to the user Sequence check, not arrival order

For each row, assert the final resource state and the side effect count, not just the status codes. A 200 from each request does not show that one update vanished or that an effect ran twice. When the client aggregates calls, also pin down the failure contract: whether one rejected request rejects the whole set and whether cancellation stops the in-flight calls.

Honest limits

A local mock runs in the desktop main process and verifies the request ordering and the client's handling of it, but it does not exercise the real server's locks, transaction isolation, or database constraints. It cannot reproduce true thread scheduling, kernel-level contention, or the full range of network jitter across separate machines. Deterministic local tests prove the code paths you can name; they do not replace load testing against a real system or a check of the actual isolation level. Importing a schema does not deploy a backend, and a passing local run is not evidence about production behavior under contention.

Frequently asked questions

Why does my concurrent test fail only sometimes? The ordering is left to the scheduler, so the dangerous interleaving appears on only a fraction of runs. A start barrier and fixed delays remove that randomness.

What is the difference between a lost update and a duplicate effect? A lost update comes from read-modify-write and overwrites a change. A duplicate effect comes from check-then-act and runs an action twice, such as a double charge.

Do I need to test both completion orders? Yes, if the client holds shared state. Responses can arrive out of order, and the client should apply the latest sequence rather than whichever call happened to resolve last.

Can a mock replace a load test? No. The mock proves the named interleavings and client logic, while a real load test checks server-side locking, isolation, and contention that the local process cannot model.

The bottom line

Treat flaky concurrent tests as a signal that the timing is still uncontrolled, then use a start barrier and fixed delays to make each interleaving a deliberate case. Assert on final state and side-effect counts across the lost-update, check-then-act, initialization, and out-of-order patterns, and verify real server-side locking and isolation separately. For more on this approach, see the API testing guide hub.