Lately, I’ve been hearing the term “race condition” more and more often – during conference talks, team discussions, or testing presentations.
Most of the time, it’s mentioned as if everyone already understands it.
In this article, we’ll break it down in plain language and look at why race conditions matter for software testers & developers.
The idea behind a race condition
A race condition happens when multiple operations try to access or modify the same resource at the same time, and the final result depends on the timing of those operations.
Think of it like two people racing to complete a task first.
If the outcome changes depending on who finishes first, you have a race condition.
In software, the “racers” are usually:
- threads
- processes
- pervices
- API calls
- users interacting with the system simultaneously
When these actions happen in the wrong order, the system may behave unpredictably.
Real-world analogy
Imagine two coworkers trying to update the same shared spreadsheet.
- Both open the file.
- Person A changes the value to 10.
- Person B changes the value to 15.
- Whoever saves last overwrites the other change.
The final value depends entirely on who saved last.
The system had no mechanism to coordinate the updates, so the result became unpredictable.
That is essentially a race condition.
How to reduce race conditions in Playwright?
A test might work perfectly on a fast local machine but fail intermittently in CI.
When timing changes, the race outcome changes.
The key idea is simple:
Wait for meaningful conditions, not time.
Avoid fixed delays like this:
await page.waitForTimeout(2000);
Instead, wait for signals that confirm the system is ready.
Wait for UI state
await expect(page.locator(‘.dashboard’)).toBeVisible();
Wait for network activity
await page.waitForResponse(resp =>
resp.url().includes(‘/orders’) && resp.status() === 200);
Wait for stable UI behavior
await expect(page.getByText(‘Success’)).toBeVisible();
These patterns make tests more deterministic.
Practical testing mindset
When writing automation tests, it helps to ask one simple question:
“What exactly tells me the system is ready for the next step?”
Not:
“Has enough time passed?”
But:
“What signal proves the operation is complete?”
The answer might be:
- a visible UI element
- a completed network request
- a database update reflected in the UI
Waiting for the right signal removes the race.
Final thoughts
Most flaky tests are simply tests that lost the race against the application.
Frameworks like Playwright reduce many timing issues with auto-waiting, but good test design is still essential.
Reliable tests are usually the ones that:
- wait for meaningful conditions
- avoid arbitrary timeouts
- synchronise with the application state
In other words, the goal is simple:
Make sure your test and your application are no longer racing each other.