Design Patterns (GoF and modern) Module 7 – Modern patterns for resources, runtime and composition
Game loop, update method and components
A fixed-timestep game loop with an injected clock, the Update Method and Component patterns, dirty flags and double buffers, and their uses outside games.
What you will learn
- Implement a fixed-timestep loop that separates simulation from rendering
- Use the Update Method and Component patterns instead of deep inheritance
- Avoid wasted work with a dirty flag and double buffering
- Recognise these patterns outside games, in simulations, dashboards and UIs
Before you start
What you’ll learn · Before you start
What you will learn
- Implement a fixed-timestep loop that separates simulation from rendering
- Use the Update Method and Component patterns instead of deep inheritance
- Avoid wasted work with a dirty flag and double buffering
- Recognise these patterns outside games, in simulations, dashboards and UIs
Before you start
On this page
A game loop runs for as long as the game does: on every pass it reads input, advances the simulation and draws a
frame. The Update Method gives every object in the world one update call per step of that loop, so each object
simulates its own behaviour. Components split those objects into small parts, such as a position, a sprite or an
AI, that can be combined freely instead of inherited from a deep class tree. Two smaller patterns keep the loop cheap
and correct: a dirty flag recomputes derived data only after its inputs change, and a double buffer lets every
update read the last state while it writes the next. The game loop’s most important decision is the timestep:
advancing the simulation in fixed steps, whatever the frame rate, makes it repeatable and testable. The same patterns
run simulations, dashboards and user interfaces.
Fixed steps make the simulation repeatable
Frames do not take the same time. A phone that renders 60 frames a second in a menu may render 20 in a busy scene, and a background tab may pause for a second. If each update moves the world by the last frame’s duration, the simulation itself changes with the frame rate. The fix is to decouple the two: the frame’s real time goes into an accumulator, and the simulation consumes it in steps of one fixed size.
A game loop with a fixed timestep
Text description of the diagram
The diagram shows one pass of the loop, from the top.
- The loop reads the player's input.
- It adds the real time the last frame took to an accumulator.
- It asks whether the accumulator holds enough time for one step and the frame is still under its cap of steps. While the answer is yes, it runs one update of the world with a fixed step of 10 ms and takes 10 ms off the accumulator, then asks again.
- When the answer is no, it renders the newest state of the world.
- A dashed arrow leads back to reading input for the next frame.
Because every update gets the same 10 ms, the simulation goes through the same states whatever the frame times are; only how often the screen is drawn changes.
Engines build this in. Godot’s documentation describes _process(), called every frame at whatever rate the device
manages, and _physics_process(), called at a fixed rate, 60 times per second by default, independent of the frame
rate. This program shows why, with a ball that falls for one second at 60 and at 20 frames per second:
"""A ball falls for one second under gravity, simulated at two frame rates. With a variable timestep, each frame
moves the simulation by that frame's duration, so the result depends on the frame rate. With a fixed timestep, the
frame time goes into an accumulator and the simulation advances in steps of exactly 10 ms, so both frame rates give
the same answer. Frame times are whole milliseconds to keep the arithmetic exact."""
G = 9.81 # metres per second squared
SMOOTH = [16, 17, 17] * 20 # 60 frames in 1,000 ms
SLOW = [50] * 20 # 20 frames in 1,000 ms
def variable_timestep(frame_times_ms):
y = v = 0.0
for frame_ms in frame_times_ms:
dt = frame_ms / 1000
v += G * dt
y += v * dt
return y
def fixed_timestep(frame_times_ms, step_ms=10):
y = v = 0.0
accumulator = steps = 0
for frame_ms in frame_times_ms:
accumulator += frame_ms
while accumulator >= step_ms: # as many 10 ms steps as the frame's time allows
v += G * step_ms / 1000
y += v * step_ms / 1000
accumulator -= step_ms
steps += 1
return y, steps
print(f"variable timestep: fell {variable_timestep(SMOOTH):.3f} m at 60 fps, {variable_timestep(SLOW):.3f} m at 20 fps")
(a, n), (b, m) = fixed_timestep(SMOOTH), fixed_timestep(SLOW)
print(f"fixed 10 ms steps: fell {a:.3f} m at 60 fps, {b:.3f} m at 20 fps ({n} and {m} steps); same: {a == b}")
print(f"exact answer: {G / 2:.3f} m") Output
variable timestep: fell 4.987 m at 60 fps, 5.150 m at 20 fps fixed 10 ms steps: fell 4.954 m at 60 fps, 4.954 m at 20 fps (100 and 100 steps); same: True exact answer: 4.905 m
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 timestep.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
With a variable timestep the ball fell 4.987 m at 60 frames per second and 5.150 m at 20: the same second of game time gave two different worlds, so a slow phone plays a different game. With fixed 10 ms steps, both frame rates ran 100 steps and the ball fell exactly the same 4.954 m. Neither matches the exact 4.905 m, which is the numerical error of the step size, but the fixed version is wrong in the same way on every machine, which is what tests, replays and multiplayer games need. With the update method on top of that loop, each entity updates once per fixed step, and with components, which parts an entity has decides which updates it gets.
The spiral of death, and capping the catch-up Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- A snake that replays identically in six languages
- Update Method and components instead of deep inheritance
- Dirty flag: recompute only what changed
- Double buffer: read the old state, write the new
- The same patterns outside games
- Check yourself
- Practise: a fixed-timestep loop with component systems
- A fixed-timestep loop with component systems
- Interview questions
- A physics-based mobile game behaves differently on slow phones: jumps go higher and objects pass through walls. The update code multiplies everything by the frame's delta time. What is wrong, and how do you restructure the loop?
- A game stores replays as the initial seed and the players' inputs. Some replays drift away from what really happened after a few minutes. List the likely causes and how you would find the first step where a replay goes wrong.
- A game has a class tree of 40 entity types, and designers keep asking for combinations such as a flying shop keeper or a crate that can be possessed. How would you move it to components and systems, and what would you measure to know it helped?
- Key takeaways
6 runnable examples (Python, Snake.java, session.py, snake.cpp, snake.go, snake.rs, snake.ts, snakes_session.py) · 1 exercise with sample tests · 3 interview answers · about 19 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
References
- Game Programming Patterns: contents (Robert Nystrom)
- Game Programming Patterns: Game Loop (Robert Nystrom)
- Game Programming Patterns: Component (Robert Nystrom)
- Game Programming Patterns: Dirty Flag (Robert Nystrom)
- Fix Your Timestep! (Gaffer On Games (Glenn Fiedler))
- Godot Engine documentation: idle and physics processing (Godot Engine)
- Window: requestAnimationFrame() method (MDN Web Docs (Mozilla))
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress