Race Conditions in Software: How to Test What Happens When Things Happen at the Same Time
Introduction
Imagine an online shopping application where only one item is left in stock.
Two customers click the Buy Now button at almost exactly the same time.
Both customers receive an order confirmation. The inventory system now shows that the item is sold out, but the company has accepted two orders for a single product.
What went wrong?
The application may have worked perfectly when tested with one user. The problem appeared only when two requests interacted with the same data at the same time.
This is a classic example of a race condition.
Race conditions are among the most challenging defects to identify in software testing. They are unpredictable, timing-dependent, and often difficult to reproduce consistently.
As Quality Engineers, we need to move beyond testing whether an operation works in isolation. We must also verify whether the system remains correct when multiple operations compete for the same resource.
1. What Is a Race Condition?
A race condition occurs when the behavior of a system depends on the timing or ordering of concurrent operations, and that ordering is not properly controlled.
In simple terms:
Two or more operations are competing to access or modify shared data, and the final result depends on which operation executes first or how their execution overlaps.
Race conditions can occur in:
Multithreaded applications
REST APIs processing concurrent requests
Database transactions
Distributed systems and microservices
Background jobs and message queues
Web applications with asynchronous operations
The important point is that a race condition is not necessarily caused by two operations executing at precisely the same instant. It can happen when their execution overlaps in an unexpected way.
A race condition can also become a security vulnerability when an attacker can exploit the timing gap between a validation and a subsequent action. This is commonly known as a Time-of-Check to Time-of-Use (TOCTOU) issue. https://community.owasp.org/pages/vulnerabilities/race_conditions
2. A Simple Example: Inventory Management
Consider an application with the following inventory:
Product: Laptop
Available stock: 1Two customers attempt to purchase the same laptop.
The expected business rule is:
The system must never accept more orders
than the available inventory.However, the application might process the requests like this:
Customer A Customer B
| |
| Read stock = 1 |
| | Read stock = 1
| Check stock > 0 |
| | Check stock > 0
| |
| Reduce stock by 1 |
| | Reduce stock by 1
| |
| Order confirmed | Order confirmedBoth requests read the same initial value before either update becomes visible.
The system has now accepted two orders when only one item was available.
This is not simply a performance issue. It is a violation of a business rule.
The key testing question is:
Can two individually valid requests produce an invalid combined result?
3. Why Are Race Conditions Difficult to Test?
Race conditions are difficult because the same test can produce different results depending on timing.
A test might pass 100 times when requests are executed sequentially, but fail when requests overlap.
Some common challenges include:
The defect may occur only within a very small time window.
Network latency affects when requests reach the server.
The issue may appear only under specific workloads.
A test may pass locally but fail in a CI/CD environment.
The API response may look correct while the database state is inconsistent.
A race may involve multiple services or asynchronous background processing.
A conventional functional test usually validates a single request and its response.
A concurrency test must validate the combined outcome of multiple operations.
This difference is important.
A successful HTTP 200 response does not necessarily mean the system handled a concurrent operation correctly.
4. Where Should QA Engineers Look for Race Conditions?
Not every endpoint needs intensive concurrency testing. The best candidates are operations that read, validate, and modify shared state.
| Application area | Potential race condition |
|---|---|
| Inventory | Two users purchase the last item |
| Payments | Duplicate payment processing |
| Banking | Two withdrawals exceed the balance |
| Coupons | A single-use coupon is redeemed twice |
| Booking | Two customers reserve the same seat |
| Insurance | Two requests create duplicate policies |
| Claims | Multiple requests approve the same claim |
| Authentication | Concurrent password reset and login operations |
| Background jobs | The same message is processed more than once |
A useful starting point is to review business rules that contain phrases such as:
Only once
One active record
Cannot exceed
Must be unique
Available until consumed
Must happen before
Cannot be processed twice
These rules often indicate that the application needs protection against concurrent operations.
5. How to Test Race Conditions
A practical testing strategy can be divided into five steps.
Step 1: Identify the business invariant
Before writing an automation script, define what must always remain true.
For example:
Given:
Available inventory = 1
When:
Two customers submit an order concurrently
Then:
Only one order must be accepted
Inventory must never become negativeThis is the most important step.
Without a clear expected outcome, a concurrency test may simply generate traffic without detecting a real defect.
Step 2: Establish a sequential baseline
First, execute the operation normally.
Verify that:
The request succeeds.
The correct database state is created.
The business rule is respected.
The response matches the expected contract.
This confirms that the basic functionality is working.
Step 3: Send concurrent requests
Now submit multiple requests against the same resource.
For example:
Request A ─────┐
├──> Inventory API
Request B ─────┘The goal is to make the requests overlap as closely as possible.
Tools such as Postman, k6, JMeter, and Burp Suite can help generate concurrent requests.
However, a high number of requests alone does not guarantee that the race window has been reached. Synchronization and controlled timing make the test more effective.
Step 4: Validate the final state
Do not stop at checking the HTTP responses.
Validate the persistent state of the application.
For example:
Expected:
Accepted orders = 1
Inventory = 0
Unexpected:
Accepted orders = 2
Inventory = -1A test should check both the API behavior and the business outcome.
Step 5: Repeat the test
Race conditions may not happen on every execution.
Repeat the test using different concurrency levels and controlled timing.
For example:
2 concurrent requests
5 concurrent requests
10 concurrent requests
50 concurrent requestsThe goal is not simply to increase traffic. It is to discover whether the system preserves its business invariants under concurrent execution.

Comments
Post a Comment