Quick answer: A race condition occurs when two or more requests act on the same data at the same time, causing an application to perform an operation that should happen once multiple times. The classic pattern is TOCTOU (time-of-check to time-of-use): the application checks a condition (coupon unused, balance sufficient) but in the tiny window between the check and the action a second request passes the same check. The result is a limit-overrun: one coupon used twice, one balance withdrawn twice, one invite creating ten accounts. The defense is not on the client, it is in the database: an atomic conditional update (one UPDATE that both checks and writes), a row lock (SELECT FOR UPDATE), a uniqueness constraint and an idempotency key. Attackers trigger this with millisecond precision using Burp's single-packet technique.

Race condition flaws are among the most insidious web vulnerabilities because the code looks correct when read in isolation: the check is there, the write is there, the logic is sound. The bug appears only when two requests collide within milliseconds, which is why it almost never shows up in normal testing. This guide explains, from an expert's view, how a race condition actually works, the most common limit-overrun examples, how an attacker triggers it reliably, and why the correct defense must always live in the data layer.

TOCTOU: the gap between check and use

Most race conditions follow the TOCTOU pattern. The application first reads a state (check), then does work based on that state (use). To a developer thinking single-threaded, this is safe. But a real server processes dozens of requests in parallel at once. If two requests arrive almost simultaneously, both pass the "check" stage against the old state, then both perform the "use" stage.

A concrete example: a gift coupon is accepted if used = false. Request A reads the coupon (false), request B also reads it (still false, because A has not written yet), both apply the coupon, both write used = true. One coupon gave a discount twice. The same pattern shows up as double spend in a balance withdrawal, unlimited invites on an invite code, inflation of a vote/like counter, and negative stock on inventory decrements.

The problem is not that the code is wrong, it is that the check and the write are not atomic. If another operation can slip in between, it will.

The most common limit-overrun scenarios

Scenario Expected Race condition result
Single-use coupon/discount Applied once N times with N parallel requests
Money/points withdrawal Up to the balance More than the balance (double spend)
Invite/referral code Up to the limit Far above the limit
Stock/reservation Sales up to stock Oversell, negative stock
Vote, like, poll One per person Inflated counter
2FA/OTP attempt limit 5 attempts Limit bypassed with parallel requests
Deposit/withdraw to account One transaction Double entry

The common denominator is always the same: a rule that "something must happen at most N times" exceeds N under parallel requests. This is why the class is an inseparable part of business-logic testing and automated scanners usually miss it; reliable detection needs a web application penetration test by someone who understands the application.

How an attacker triggers it: the single-packet attack

Triggering a race condition used to be hard because sending requests at the same time suffered from network jitter. The modern technique solved this: HTTP/2's single-packet attack delivers the final bytes of many requests in one TCP packet at the same instant, removing network latency from the equation so the requests are processed on the server in near-perfect alignment. For HTTP/1.1, last-byte synchronization achieves a similar effect.

In practice the test is done with the Burp Suite tab that implements this technique; one click sends 20 to 50 aligned requests, and if the result shows more "successful" responses than expected the flaw is confirmed. We cover the tooling in our Burp Suite guide. The key point: race condition detection is a verifiable flaw; one "successful" response is not enough, you must prove that the same one-time operation happened multiple times.

The correct defense: always in the data layer

Trying to fix a race condition with an if in application code almost always fails, because the gap is exactly between that if and the following write. The correct defense is at the database level:

  • Atomic conditional update. Combine the check and the write into one statement. For example UPDATE coupon SET used = true WHERE id = ? AND used = false, and if the affected row count is 1 proceed, if 0 reject. This leaves no gap between check and write.
  • Row lock. Within a transaction, lock the relevant row with SELECT ... FOR UPDATE; the second request waits until the first finishes.
  • Uniqueness constraint. Enforce rules like "one vote per user" or "one use per coupon" with a database UNIQUE constraint; the second insert fails.
  • Idempotency key. Have the client attach a unique key to every critical operation; if the server sees the same key a second time it does not repeat the operation but returns the first result. This is the standard method of payment APIs.
  • Distributed lock. If there are multiple servers/instances and a single database row lock is not enough, use a Redis-based distributed lock (for example one key plus a TTL).

Rate limiting alone is not sufficient because the race window can fit inside a single rate-limit interval; it still helps as defense in depth. We detailed the API-side design in our API security and OWASP API Top 10 article.

TOCTOU at the system level: not just the web

TOCTOU is not unique to web applications. The same pattern appears in the file system: a program checks a file's ownership or existence, then opens it; if an attacker swaps the file for a symbolic/hard link in between, the program writes to the wrong file. This is classified as CWE-367 and has serious examples. For instance, a 2026 flaw affecting the Debian/Ubuntu apt package manager (CVE-2026-109518) could lead to installation of an unverified package and code execution with root privileges due to hard-link reuse and a race condition during re-downloads. The lesson is the same: every gap between the moment you make a security decision and the moment you act on it is an attack window.

The KAOS and DSET approach

At DSET we treat race condition flaws as a first-class target of business-logic testing, because they are among the flaws automated scans most often miss yet carry the highest financial impact. In our tests we stress critical one-time operations (coupon, withdrawal, invite, stock) with parallel requests using the single-packet technique, and we do not merely detect the flaw, we verify it by proving the same operation happened multiple times. Our KAOS tool flags non-atomic check-then-write patterns on critical endpoints and missing idempotency/lock markers, but it always ties the final verdict to proven reproduction and does not produce an inflated "vulnerability found" claim. To have your application's critical operations tested against race conditions, contact DSET and reach our team at Ankara Hacettepe Teknokent. Related reading: the difference between vulnerability scanning and penetration testing.

Frequently asked questions

Are race condition and TOCTOU the same thing? Race condition is the general concept: an unpredictable result that depends on the timing of two operations. TOCTOU is the most common security pattern of this concept: the state changing between checking a condition and acting on that condition. The majority of web flaws take the TOCTOU form; so do file-system races. In practice the two are used interchangeably.

Does rate limiting prevent race conditions? Not on its own. Rate limiting caps the number of requests per unit time, but the race window is within milliseconds and multiple requests can collide inside a single allowed interval. Rate limiting contributes to defense and makes brute-force attempts harder, but the real fix is atomic operations, row locks, uniqueness constraints and idempotency keys.

Can an automated scanner find a race condition flaw? Usually not. A scanner sends one request and looks at the response; a race condition requires multiple requests at the same time plus business-logic knowledge. Reliable detection needs a manual penetration test that understands the application's one-time operations and sends parallel requests with the single-packet technique. This is why critical financial flows must be tested by hand.

Sources