“It Works on Refresh”
Race Conditions and Timing Bugs in the Browser
You open a page and something is broken. A button does nothing. A canvas is blank. An error appears in the console.
You press refresh.
Everything works.
The Browser Is Doing More Than You Think
When a page loads, the browser is juggling multiple tasks at once:
- Parsing HTML
- Building the DOM
- Downloading scripts
- Executing JavaScript
- Loading images, fonts, and media
These steps overlap. They don’t always happen in the neat, top-to-bottom order we imagine when reading code.
The Classic Timing Bug
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
Sometimes this works. Sometimes it fails with:
Uncaught TypeError: Cannot read properties of null
The problem is not the API.
The problem is that canvas does not exist yet.
Why Refreshing Changes the Outcome
When you refresh the page, several things change:
- Files may load faster from cache
- Execution order shifts slightly
- The timing of events changes
If your bug depends on which thing finishes first, a refresh can make it disappear — without actually fixing anything.
Race Conditions (Without the Scary Definition)
A race condition happens when the correctness of your code depends on timing.
In the browser, the race is often between:
- Your JavaScript code
- The browser finishing its setup work
If your code arrives too early, it loses the race.
Seeing the Problem Visually
This short video does a great job of visualizing how JavaScript execution, the DOM, and timing interact in the browser:
Watching this once makes timing bugs feel far less mysterious.
The Correct Way to Synchronize
Instead of hoping your code runs at the right time, tell the browser explicitly when it is allowed to run:
document.addEventListener("DOMContentLoaded", () => {
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
});
This is not a workaround. This is synchronization.
Takeaways
- If a bug disappears on refresh, suspect timing
- Network success does not mean execution readiness
nulloften means “not yet”- Good browser code waits — it doesn’t rush