Why your browser game lags
A game that stutters is usually not doing too much work. It is usually doing a small amount of the wrong work, many thousands of times a second.
Jump to a section
Before optimising anything, check whether the game is running twice. A restart that calls the loop again without stopping the first one is the most common cause of a game that suddenly feels wrong. It is not a performance problem at all. After that, nearly every slow browser game is slow for one of four reasons, and you can find which without a profiler.
A regular hitch every few seconds is rubbish being collected, which means something is being created every frame. A constant low frame rate with no hitches is too much work per frame, usually drawing. Tell them apart first, because the fixes have nothing in common.
First, check it is not running twice#
This one is worth its own section because it is extremely common in generated code and it does not look like a bug. The game works. You lose, you press play again, and now everything moves at double speed while the frame rate drops.
What happened is that the restart called the function that starts the animation loop, and the original loop was never stopped. Two loops now run, both updating the same world, so everything moves twice per frame.
let running = false;
function start() {
if (running) return; // the guard that fixes it
running = true;
requestAnimationFrame(frame);
}
function stop() { running = false; }
function frame(now) {
if (!running) return; // the loop ends itself
update(now);
draw();
requestAnimationFrame(frame);
}
Press restart five times in a row. If the game gets faster each time, this is your bug, and nothing else on this page applies to you.
The five things that are actually slow#
- Objects and arrays made every frame. A new position object per mover, an array built to hold what to draw, a string joined for the score. Each is tiny and each happens thousands of times a second. Write into things that already exist instead.
- Drawing what nobody can see. A world three screens wide drawn in full every frame costs three times what it needs to. Skip anything whose position is outside the visible area before you draw it.
- Soft edges. Shadow blur, glows and filters are far more expensive than flat shapes, and in a generated game they are usually applied per object rather than once. A glow on two hundred particles can cost more than everything else combined.
- Gradients rebuilt every frame. A gradient is an object you are supposed to make once and keep. Made inside the draw call, it is created and thrown away sixty times a second.
- Touching the page. Writing the score into an element every frame is slow, and reading anything about an element's size is slower, because it forces the browser to work out the layout then and there.
Finding yours in five minutes#
You do not need to know how to read a profiler. You need to cut things out until it goes fast, which is faster than understanding why.
- Stop drawing. Comment out the body of the draw function. If the game is suddenly fast, the problem is drawing. If it is still slow, it is the update step.
- Halve the world. Set the number of enemies or particles to a tenth. If that fixes it, the cost is per object, and the fix is to draw fewer or to stop drawing the ones off screen.
- Turn off the pretty. Remove shadow blur, glows and filters. This one alone resolves a surprising share of slow games.
- Check the canvas size. A canvas set to the screen size multiplied by a high device pixel ratio can be drawing nine times as many pixels as you think on a phone.
- Watch for the pattern. Even slowness is work per frame. A hitch every few seconds is rubbish being collected.
Once you know which of the five it is, the fix is a sentence. Paste the finding rather than the symptom: the slowdown is in the draw step and it goes away when I remove shadow blur gets a correct answer immediately.
What not to bother with#
Generated code often arrives with optimisations that cost readability and buy nothing at this scale.
- Micro-optimising arithmetic. Replacing a divide with a multiply does not matter when you are drawing two hundred sprites with a glow on each.
- Workers and offscreen canvases. Real tools, wrong scale. A browser game of this size has not earned either, and both make everything harder to change.
- Caching things that are cheap. Storing a value to avoid recomputing something trivial adds a thing that can go stale, for no gain.
- Chasing the last few frames. If it holds a steady rate while playing, it is finished. Nobody has ever stopped playing a browser game because it was not faster.
For a list of bullets that grows forever, the shooter guide covers reusing them properly. The 2D guide covers sizing the canvas so it is sharp without being enormous.
The game slows down. Here is what I have established by testing:
<one line: it is the draw step, or the update step, or a hitch every
few seconds, or it gets faster when I reduce the number of objects>
Fix that specific cause. While you are in there:
- Make sure only one animation loop can ever be running, with a guard
so restarting cannot start a second one.
- Do not create objects, arrays or strings inside the update or draw
steps. Reuse what already exists.
- Skip drawing anything outside the visible area.
- Build any gradients once at startup rather than per frame.
Do not add workers, offscreen canvases or a new rendering approach.
Change as little as possible and tell me in two lines what you changed.
The last line matters. Performance work is where a model is most tempted to rewrite the renderer, which is how a slow game becomes a broken one.
🐌Games on this site
Common questions#
Why does my browser game stutter every few seconds?
Something is being created every frame and the browser is pausing to clear it up. Look for new objects, arrays or joined strings inside the update or draw steps, and write into existing objects instead. Even slowness has a different cause from a regular hitch.
Why does my game speed up after I restart it?
Because two animation loops are running. The restart started a new one without stopping the old one, so everything updates twice per frame. Add a guard so the loop cannot be started while it is already running.
Why is my game slow on a phone but fine on a laptop?
Usually the canvas. A phone with a high device pixel ratio can be drawing several times as many pixels as your laptop, so the same drawing code costs much more. Check the size you are actually setting on the canvas.
Do I need a game engine to get better performance?
No. At the scale of a browser game, an engine is not faster than plain canvas, and the causes on this page would still be the causes. An engine solves scenes, assets and animation, not frames.
How many objects can a browser game handle?
Far more than most games need, provided each one is cheap. A few thousand flat shapes is comfortable. Two hundred with a shadow blur on each is not, which is why the cost per object matters more than the count.
Should I use a fixed time step to fix stuttering?
Only if the stutter is in the physics rather than in the frame rate. A fixed step solves inconsistent simulation, not a slow frame, and applying it to a frame rate problem usually makes the game feel worse.
Where to go next#
The one-file skeleton keeps the update and draw steps separate. That separation is what makes the first test on this page possible at all.
When it holds a steady frame rate on a phone, it is ready for other people. Publish it, and it gets a page recording the model and the prompt behind it.
Built something? Put it on Agent Games.
Publishing takes a file and a minute. Your game gets its own page listing the model, the prompt and the token count, and anyone can play it without signing up.
Read next