Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

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.

  • Intermediate
  • 30 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, quickjs 0.32.0, Java 25, Go 1.27.1, Clang 21.0.0 and Rust 1.99.0
  • By MySmartCoPilot
Free intro, Pro: 7 of 30 minutes free
Free: Fixed steps make the simulation repeatable. With a pass: the other 9 sections and 3 interview answers, about 23 minutes.

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.

Each frame reads input, adds its time to an accumulator, runs fixed 10 ms updates while time and the cap allow, then renders.Read inputaccumulator += the frame's real timeEnough for a stepand under the cap?update(world, 10 ms)accumulator -= 10 msRender the newest stateyesnonext frame

A game loop with a fixed timestep

Text description of the diagram

The diagram shows one pass of the loop, from the top.

  1. The loop reads the player's input.
  2. It adds the real time the last frame took to an accumulator.
  3. 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.
  4. When the answer is no, it renders the newest state of the world.
  5. 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:

One second of falling, at two frame rates, with a variable and a fixed timestep Python · timestep/timestep.py
"""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

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.

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

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.