Every video game, from the simplest Pong clone to the most ambitious 3D title, revolves around a cycle that repeats dozens of times per second: the game loop. Understanding how it is built is the first step towards writing games that behave the same way on a modest laptop and on a gaming PC.
The three phases of the loop
In its most basic form, the game loop performs three operations in order:
- Reading input: keyboard, mouse, gamepad, touch.
- Updating state: positions, velocities, scores, timers, enemy AI, collisions.
- Rendering: drawing the scene on screen from the state just computed.
In pseudocode:
while (running) {
read_input()
update()
draw()
}
The problem with this version is that the game's speed depends on the machine's speed. If each call to update() moves the character by 2 pixels, on a computer running the loop 300 times per second the character runs five times faster than on one that manages 60.
Variable time and its limits
The first fix is to measure how much time has passed since the last frame (the so-called delta time) and multiply every movement by that value. That way the character covers, say, 120 pixels per second regardless of frame rate.
This works well for animations and simple movement, but it shows its limits with physics. If the time step keeps changing, the results change with it:
- a jump can reach slightly different heights depending on the frame rate;
- a fast object, during a long frame, can pass through a thin wall without the collision being detected;
- simulations with springs and constraints can become unstable and «explode» when the delta is large.
The game also becomes non-deterministic: the same input sequence does not always produce the same result, which complicates replays, multiplayer and automated testing.
Fixed timestep with an accumulator
The most widespread solution separates simulation time from rendering time. Logic is always updated with the same step, for example 1/60 of a second, while rendering happens whenever it can. The link between the two worlds is an accumulator:
const STEP = 1.0 / 60.0
accumulator = 0
previous = now()
while (running) {
current = now()
elapsed = current - previous
previous = current
accumulator += elapsed
read_input()
while (accumulator >= STEP) {
update(STEP)
accumulator -= STEP
}
draw()
}
If rendering is slow and 33 milliseconds pass between two frames, the inner loop runs two updates. If it is very fast, some frames run no update at all and redraw the same state. Physics only ever sees identical steps, so it is stable and repeatable.
Interpolating to remove stutter
A fixed step introduces a visual flaw: if the screen runs at 144 Hz and the logic at 60, many frames show the same state and movement looks slightly jerky. The remedy is interpolation. You keep the previous and current state of each object and, at draw time, compute an intermediate position:
alpha = accumulator / STEP
drawn_position = previous * (1 - alpha) + current * alpha
The value alpha, between 0 and 1, tells you how far «ahead» you are of the last update. The player sees smooth motion while the simulation stays on a fixed step. The cost is a visual delay of at most one step, usually imperceptible.
The spiral of death
There is one case to handle explicitly. If a single update takes longer than the step itself, the accumulator grows every iteration, the inner loop runs more and more updates, and the game slows down until it freezes. This is the so-called spiral of death. The defence is simple: cap the elapsed time considered in a single frame, for example at a quarter of a second. Under overload the game slows down for a moment instead of collapsing.
How engines handle it
Modern engines already provide this separation. Unity has Update, called once per frame, and FixedUpdate, called on a fixed step for physics. In Godot the distinction is between _process and _physics_process. Knowing what happens behind the scenes helps you decide where to put your code:
| Type of logic | Where to put it |
|---|---|
| Physics-driven movement, collisions | Fixed-step update |
| Reading input | Every frame, storing the state |
| Graphic animations, effects, UI | Every frame with delta time |
| Game timers that matter | Fixed step, for consistency |
A common mistake is reading a «one-shot» key press inside the fixed-step update: if no update runs in that frame, the press is lost. It is better to record the event in the frame and consume it at the first available step.
Vertical sync and power use
Finally, a loop that runs unchecked uses one hundred percent of a core even to draw a static menu. Enabling vertical sync, or adding a short wait when you are ahead of schedule, reduces power draw, heat and fan noise.
In short
- The game loop reads input, updates state and draws, dozens of times per second.
- Delta time makes movement independent of frame rate, but is not enough for stable physics.
- A fixed timestep with an accumulator makes the simulation stable and repeatable.
- Interpolating between previous and current state removes visual stutter.
- Capping time per frame prevents the spiral of death.