Concurrent application behavior

Preventing Race Conditions in Web Apps

Find read-modify-write races, stale responses, duplicate work, and lost updates across browser, server, and database boundaries.

How this page is maintained

Written for learners, checked against the sources below, and reviewed every year. Last reviewed July 27, 2026.

Short answer

A race condition occurs when correctness depends on the timing or ordering of concurrent operations. Prevent it by naming the shared state and invariant, reproducing the competing sequence, and enforcing order or atomicity at the layer that owns the state. Options include constraints, conditional writes, transactions, queues, cancellation, version checks, and idempotent handling.

Who this is for: Frontend and backend developers debugging defects that appear only when requests, jobs, or user actions overlap.

  • Draw the competing operations as a timeline and identify the exact stale read or conflicting write.
  • Protect server-owned invariants on the server or database, even when the interface also prevents duplicate actions.
  • Choose cancellation, sequencing, deduplication, or conflict detection according to the intended product behavior.

Recognize races beyond threads

Web races arise from independent requests, multiple browser tabs, queued jobs, retries, and asynchronous callbacks. JavaScript running one callback at a time can still race because an operation pauses for input and another operation changes state before the first resumes. Distributed services make timing less predictable, but the underlying issue remains ordering around shared state.

Typical symptoms include duplicate orders, a slower search response replacing a newer one, two editors overwriting each other, or a worker processing the same message twice. Do not diagnose from frequency alone. Identify the state, invariant, operations, and ordering that produces the wrong committed or displayed result.

Protect authoritative state atomically

Suppose two requests read stock of one and both subtract one. A client-side disabled button reduces accidental clicks but cannot stop two devices or retries. The authoritative write should include the condition, such as updating only where stock remains positive, and verify that exactly one row changed. A database constraint can provide another guard.

For more complex invariants, use an appropriate transaction and locking or version strategy. A queue can serialize work for a particular key, but it adds latency and requires partitioning and recovery design. A distributed lock introduces expiration and ownership problems, so prefer an atomic operation in the state owner when one is available.

Control stale work in the interface

Search suggestions often issue a request for each query. The response for an older query may arrive after the latest response. Abort obsolete requests when possible and compare a request generation or current query before applying results. Cancellation saves work, while the comparison remains useful because completion and cancellation can race.

For edits, optimistic interfaces should distinguish temporary local state from confirmed server state. Include a version or validator with the update so the server can reject a stale write. Then offer a deliberate reload, merge, or retry experience. Last-write-wins is acceptable for some low-value preferences but dangerous for documents, balances, and approvals.

Test schedules instead of hoping

Create a deterministic test hook around the vulnerable boundary. Pause two operations after their reads, release them in a chosen order, and assert the invariant. For interfaces, delay the older response until after the newer response. Run duplicate deliveries and repeated requests using the same logical operation identifier.

Production evidence should include request or job identifiers, resource versions, attempt number, and conflict outcome without sensitive payloads. Metrics for uniqueness violations, stale-write conflicts, duplicate suppression, and queue redelivery reveal pressure. A race that cannot be observed is likely to return after a superficial timing fix.

Fix out-of-order account search

A user types ann and then anna, but the slower response for ann replaces the correct newer results.

  1. Assign each search request a monotonically increasing local generation and capture the query it represents.
  2. Abort the previous fetch when a new query starts to reduce unnecessary network and server work.
  3. When a response resolves, compare its generation and query with the current values before changing displayed results.
  4. Treat abort as an expected outcome rather than showing it as an application failure.
  5. Test by delaying the ann response until after anna and assert that only anna results remain visible.
Result: Network completion order no longer determines the visible search state, and obsolete work is cancelled when the platform permits it.

Race investigation timeline

Fill one line for each competing operation and shared state transition.

  • Invariant and owner: what must remain true, and which system is authoritative for that state.
  • Operation A: initial version, read, pause, intended write, commit, and returned result.
  • Operation B: initial version, read, pause, intended write, commit, and returned result.
  • Control choice: atomic condition, transaction, version, queue, cancellation, deduplication, or accepted last write.
  • Proof: forced ordering test, production signal, conflict behavior, and recovery for already inconsistent data.

Common mistakes

  • Adding a short delay and assuming changed timing has removed the underlying race.
  • Using a disabled browser button as the only protection for a server-side invariant.
  • Introducing a distributed lock without defining ownership, lease expiry, fencing, or crash recovery.

Try one

Two workers may receive the same export job, and only one file should become the official result. Propose a design and test.

A strong design gives the logical job a unique key, atomically claims or creates one result record, and makes repeated processing return or verify the same outcome. File publication should use a temporary object followed by a conditional authoritative update. The test pauses two workers after claim attempts, varies completion order, and asserts one official result plus recoverable cleanup.

Sources

Learn this with a tutor

Tell LearnLive what you already know and what you need to do with preventing web race conditions.

Build this course