Fixed Timestep Game Loops in the Browser: Decoupling Simulation From Frame Rate

You probably assume that updating your game's physics inside requestAnimationFrame is the natural, correct way to build a loop — one update per frame, one frame per repaint, clean and simple. However, that single decision is the most common reason a browser game that feels flawless on a 60 Hz laptop turns into a slow-motion crawl or a fast-forward blur the moment someone opens it on a 144 Hz or 240 Hz display.
The fix is not a faster machine or a heavier physics library. It is a decades-old pattern — the fixed timestep accumulator — that separates how often your simulation advances from how often the screen actually draws.
This post walks through why frame-rate-coupled physics breaks, how the accumulator keeps your simulation deterministic across every device, and the production gotchas that bite you in a real browser tab. Keep in mind that the same pattern underpins reliable multiplayer, replay systems, and anything where two machines need to agree on what happened.
What Is A Fixed Timestep Game Loop?
A fixed timestep game loop advances your simulation in constant, equal increments of time — for instance, exactly one sixtieth of a second per step — no matter how fast or slow the display is refreshing. Rendering still happens as often as the browser will allow, but the physics never sees a variable delta.
This is the opposite of the naive approach, where each update uses however much wall-clock time elapsed since the previous frame. That elapsed value, commonly called delta time, is exactly the thing that makes a frame-rate-coupled game behave differently on different hardware.
A fixed timestep loop advances simulation in constant increments — typically 1/60 of a second — regardless of refresh rate. Rendering runs as fast as the display allows, but physics always sees the same fixed delta.
Why Does Tying Physics To Frame Rate Break Your Game?
When your update step multiplies movement by the elapsed delta, you are trusting that delta to be accurate, stable, and well-behaved — and in a browser, it is none of those things. A 60 Hz monitor delivers a frame roughly every 16.67 milliseconds, while a 144 Hz monitor delivers one about every 6.94 milliseconds, and a 240 Hz panel every 4.17 milliseconds or so.
If your physics runs once per frame, the 240 Hz player's simulation advances nearly four times as often as the 60 Hz player's. Velocity-by-delta math hides this for simple linear motion, but the moment you introduce gravity, friction, or any force that compounds, the integration error diverges and the two players see different outcomes.
The deeper problem is floating-point accumulation. Adding many small deltas produces a different result than adding fewer large ones, so even a single-player game becomes non-reproducible — a recorded input sequence will not replay the same way at a different refresh rate.
There is also the stutter case. A single dropped frame doubles your delta for one update, and a physics step that suddenly integrates 33 milliseconds instead of 16 can tunnel a fast object straight through a wall.
Frame-rate-coupled physics breaks because update frequency tracks refresh rate. A 240 Hz display steps four times more often than 60 Hz, so compounding forces and floating-point error diverge between devices.
What Is The Accumulator Pattern, And How Does It Work?
The accumulator pattern solves all of this with a single buffer variable and a constant step size. Instead of stepping the simulation once per frame, you measure real elapsed time, add it to an accumulator, and then drain that accumulator in fixed-size chunks.
Here is how a single iteration of the loop works, in order:
- Measure real elapsed time. Read a high-resolution timestamp from performance.now and subtract the previous frame's timestamp to get the true wall-clock delta.
- Add it to the accumulator. The accumulator is a running total of unsimulated time that has not yet been consumed by a physics step.
- Step while you can afford to. While the accumulator holds at least one fixed timestep, advance the simulation by exactly that constant and subtract it from the accumulator.
- Render once. After the accumulator drops below one full step, draw the frame using whatever simulation state you now have.
The result is that a 60 Hz player runs roughly one physics step per frame, while a 240 Hz player runs about one step every four frames — yet both advance the simulation by precisely the same amount of time per real-world second. The simulation is now deterministic and the rendering rate is free to float.
The accumulator stores real elapsed time and is drained in fixed-size chunks. Each frame you add the measured delta, step physics by a constant while a full step remains, then render once afterward.
Why Does Rendering Need Interpolation?
After the loop drains the accumulator, there is almost always a small remainder — a fraction of a timestep that was not large enough to trigger another physics step. If you render the raw simulation state and ignore that remainder, fast-moving objects will visibly stutter, because the screen is refreshing at moments that fall between two discrete physics positions.
The remedy is interpolation. You compute an alpha value — the leftover accumulator divided by the fixed timestep — and render each object at a blend between its previous and current physics positions weighted by that alpha.
This is why a well-built fixed timestep game keeps both the previous and the current state of every moving object. The simulation stays discrete and deterministic, while the visuals stay smooth on any refresh rate, including the high-Hz panels that exposed the bug in the first place.
Rendering needs interpolation because the accumulator leaves a sub-step remainder each frame. Blend each object's previous and current position by alpha — the leftover time divided by the timestep — to erase stutter.
What Is The Spiral Of Death, And How Do You Stop It?
The naive accumulator has a fatal failure mode known as the spiral of death. If a single physics step ever takes longer to compute than the fixed timestep it represents, the accumulator grows faster than the loop can drain it, which forces even more steps on the next frame, which falls even further behind.
Left unchecked, the tab freezes as the loop tries to simulate an ever-growing backlog. This is especially common in the browser, where a garbage-collection pause or a backgrounded tab can hand you a delta measured in whole seconds.
The standard defense is a clamp. Before adding the measured delta to the accumulator, cap it at a sane maximum — often around 250 milliseconds — so a long stall is treated as a brief hiccup rather than a debt the simulation can never repay.
Many engines add a second guard: a hard ceiling on the number of substeps per frame. When that cap is hit, the game simply accepts a little slow-motion for one frame instead of locking the main thread, which is almost always the better trade.
The spiral of death occurs when a physics step costs more than the timestep it represents, so the accumulator outgrows the loop. Clamp measured delta to about 250 ms and cap substeps per frame to stop it.
How Do You Choose The Right Fixed Timestep?
The most common choice is one sixtieth of a second, or about 16.67 milliseconds, because it matches the baseline refresh of most displays and keeps integration stable for typical platformer and arcade physics. A smaller step — say one hundred-twentieth of a second — improves collision accuracy for fast objects at the cost of roughly double the simulation work.
The trade-off is straightforward: smaller steps mean more accuracy and more CPU, while larger steps mean cheaper frames and coarser physics. Note that whatever value you pick must stay constant for the lifetime of the simulation, because the determinism guarantee depends on every machine using the identical step.
Here is how the two approaches compare across the dimensions that matter for a browser game:
| Dimension | Variable timestep (frame-coupled) | Fixed timestep (accumulator) |
|---|---|---|
| Behavior across refresh rates | Differs on 60 / 144 / 240 Hz | Identical on every display |
| Determinism and replays | Not reproducible | Stable and replayable |
| Tunneling on stalls | Common with large deltas | Prevented by clamping |
| Rendering smoothness | Smooth by default | Smooth with interpolation |
| Implementation cost | Trivial | Modest — one buffer plus state copies |
How Does Determinism Affect Multiplayer?
Determinism is not just a single-player nicety — it is the foundation of lockstep multiplayer, where every client simulates the same world from the same inputs instead of constantly syncing positions over the wire. If two clients run different timesteps, their simulations drift apart within seconds, and no amount of bandwidth will paper over the divergence.
A fixed timestep is therefore a prerequisite for the kind of deterministic netcode discussed in our guide to WebRTC multiplayer for browser games, and it pairs naturally with the timing discipline covered in input buffering for browser games. Both rely on the simulation advancing in predictable, identical increments on every connected machine.
The same property powers replays and killcams. Because a recorded input stream plus a fixed step reproduces the match exactly, you can store a few kilobytes of inputs instead of gigabytes of state.
What Breaks This Pattern In A Real Browser?
The browser introduces timing hazards a native engine never has to think about. The most important is that requestAnimationFrame stops firing entirely when a tab is backgrounded or the device sleeps, so the moment the tab regains focus you receive one enormous delta representing all the elapsed time.
This is exactly the case your spiral-of-death clamp exists to absorb, and it is why you should listen for the document visibilitychange event and reset your timestamp on return. Without that reset, your first foreground frame tries to simulate minutes of backlog in one go.
Where the simulation runs also matters. Moving the loop off the main thread — the technique covered in OffscreenCanvas and Web Workers — keeps a heavy physics step from blocking input and paint, and it composes cleanly with a data-oriented design like the one in an entity component system for browser games.
Finally, always drive your delta from performance.now rather than from frame counts, and never assume a frame equals 16 milliseconds. The whole point of the pattern is that you stop trusting the frame as a unit of time.
The one-line rule: simulate in fixed steps, render with interpolation, and clamp the accumulator so a backgrounded tab can never bankrupt your physics.
How Do You Verify Your Loop Is Working?
The fastest sanity check is to run the same recorded input sequence twice and confirm the final world state is identical down to the last value. If a replay diverges, you still have a hidden source of variable time somewhere in the update path — a stray requestAnimationFrame delta, a non-deterministic iteration order, or an unseeded random number generator.
The second check is a refresh-rate sweep. Cap the render loop at 30, 60, and 144 frames per second and confirm that an object launched with the same force lands in the same place every time.
Be aware that determinism across different CPUs also depends on consistent floating-point behavior, which is one reason performance-critical browser games increasingly move their simulation into WebAssembly game engines. If cross-machine reproducibility is a hard requirement, treat the math layer with the same rigor as the loop itself.
Bringing It Together
Decoupling simulation from frame rate looks like extra plumbing right up until the first bug report from a player on a 165 Hz handheld — and then it looks like the only sane way to build. The accumulator is perhaps fifteen lines of code, and it is the difference between a game that behaves and one that quietly drifts.
If you are wiring up the render layer alongside this loop, our comparison of Canvas versus WebGL for browser games covers where to spend your per-frame budget. Build the fixed timestep first, and every input, physics, and networking system you stack on top inherits its determinism.
Frequently Asked Questions
A few common questions about implementing fixed timestep loops in production browser games.
Does a fixed timestep make my game run slower?
No — it decouples simulation rate from render rate, so rendering still runs as fast as the display allows. Only the physics advances in constant steps, and that work is usually a small fraction of a frame's budget.
What is a good default fixed timestep value?
One sixtieth of a second, about 16.67 milliseconds, is the standard default for most arcade and platformer physics. Drop to one hundred-twentieth of a second when fast objects need tighter collision accuracy.
Do I still need interpolation if I run physics at 120 Hz?
Usually yes, because the render refresh rarely lines up exactly with physics steps and the leftover accumulator still causes micro-stutter. Interpolation blends previous and current state to keep motion smooth.
How do I stop the spiral of death?
Clamp the measured delta before adding it to the accumulator — roughly 250 milliseconds is a common ceiling — and optionally cap substeps per frame. A long stall then becomes a brief slowdown instead of a frozen tab.
Why does my game speed up on a 144 Hz monitor?
Because your physics is tied to frame rate, so more frames per second means more updates per second. A fixed timestep accumulator advances the simulation by the same amount of time regardless of refresh rate.


