The Game Loop

March 14, 2026

The game loop is the fundamental structure in game development. It is the outermost loop that encompasses all runtime operations; input handling, physics updates, and rendering. The number of physics updates per second is referred to as ‘ticks’, and the number of renders per second is the ‘frame rate’ or ‘frames per second’ (fps).

In simple games, these are usually coupled. Each loop iteration handles input, runs one physics step, and renders one frame. The game loop rate equals ticks equals fps. This is straightforward to implement.

game_state = {}

while True:
    input_queue = read_input()
    physics_step(input_queue, game_state)  # Updates game_state
    render(game_state)
    clock.tick(60)  # Cap at 60 iterations per second

Here, everything runs at 60 iterations per second. Simple, but inflexible. If the display and GPU can render at 144 fps, you’re leaving performance on the table. If the CPU can’t sustain 60 iterations per second, everything slows down together.

In more sophisticated designs, ticks and frames are decoupled from the loop. Physics runs at a fixed rate using a time accumulator, rendering runs at the display’s refresh rate, and the loop orchestrates both.[1]

TICK_RATE = 60
FRAME_RATE = 144

tick_duration = 1.0 / TICK_RATE
frame_duration = 1.0 / FRAME_RATE

tick_accumulator = 0.0
frame_accumulator = 0.0

input_queue = []
game_state = {}

while True:
    delta_time = clock.tick() / 1000.0
    tick_accumulator += delta_time
    frame_accumulator += delta_time

    input_queue.extend(read_input())  # Always read input every iteration

    while tick_accumulator >= tick_duration:
        input_queue.extend(read_input())  # Catch input during long catch-up frames
        physics_step(input_queue, game_state)  # Updates game_state
        input_queue.clear()  # Consume input after processing
        tick_accumulator -= tick_duration

    if frame_accumulator >= frame_duration:
        render(game_state)
        frame_accumulator -= frame_duration

Physics runs at a fixed 60 ticks per second regardless of frame rate. Rendering adapts to hardware capability. The benefits are twofold; physics remains deterministic, and faster hardware gets smoother visuals without affecting gameplay.

Game engines like Unity and Unreal hide the game loop from you. The engine owns the loop and manages timing and orchestration internally. What you get instead are hooks; methods that the engine calls at specific points in the loop.

In Unity, the primary hooks are:

  • FixedUpdate() — Called at a fixed rate, independent of frame rate. This is where physics code goes. It corresponds to physics_step in our examples above.
  • Update() — Called once per frame. This is where frame-dependent logic goes; input handling, animations, game state that doesn’t need fixed timing.
  • LateUpdate() — Called after all Update() calls. Useful for operations that depend on other objects being updated first, like a camera following a player.

Rendering is handled automatically by the engine’s graphics pipeline, though hooks like OnPreRender() and OnPostRender() exist for custom rendering logic.

This abstraction means you write game logic without worrying about the loop itself. The engine guarantees FixedUpdate() runs at a consistent rate, that Update() runs every frame, and that rendering happens at the right time. The tradeoff is less control. You trust the engine to get the timing right, and in return you get to focus on gameplay.

The same pattern appears in web frameworks. Take Spring Boot, for instance. The framework owns a loop; in this case, the embedded server (Tomcat, Jetty) listening for incoming HTTP requests. You never write this loop yourself. Instead, you provide handlers that the framework invokes when requests arrive.

@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
    return userService.findById(id);
}

This is a hook. The framework receives the request, parses it, matches it to your handler, and calls your method with the appropriate arguments. You write business logic; the framework handles sockets, threads, parsing, and response serialization.

Schedulers work similarly. Instead of writing a loop that checks the clock and runs tasks at intervals, you declare intent and let the framework manage timing.

@Scheduled(fixedRate = 60000)
public void cleanupExpiredSessions() {
    sessionService.removeExpired();
}

This is analogous to FixedUpdate() in Unity; a hook that runs at a fixed rate, decoupled from request handling. The framework maintains the timing loop internally.

Browsers follow the same paradigm. The browser runs an event loop that processes input, executes JavaScript, and renders the page. You never write this loop. You only provide hooks.

For input, you register event listeners.

button.addEventListener('click', (event) => {
    handleClick(event);
});

The browser detects the click, constructs an event object, and calls your handler. You write the response; the browser handles event propagation, and timing.

For rendering, requestAnimationFrame hooks into the browser’s render cycle.

function animate() {
    updatePosition();
    draw();
    requestAnimationFrame(animate);
}

requestAnimationFrame(animate);

This is the browser’s equivalent of Update() in Unity. The callback runs once per frame, synchronized with the display’s refresh rate. The browser decides when to call it; you provide what to do.

For scheduled tasks, setInterval provides fixed-rate execution.

setInterval(() => {
    checkForUpdates();
}, 5000);

The browser maintains the timing internally. You declare the interval and the handler.[2]

The event loop, like the game loop, is the fundamental structure. Input events queue up and dispatch to listeners. Animation callbacks run each frame. Timers fire at their scheduled intervals. The browser orchestrates all of it. You hook in where needed.

HTML rendering itself follows this pattern. You declare structure and style; the browser handles layout, paint, and compositing. When you modify the DOM, you do not call a render function. The browser detects the change and schedules a re-render automatically.

element.textContent = 'Updated';  // Browser re-renders for you

You describe what the UI should look like. The browser decides when and how to draw it. You don’t interface with the render loop directly.

Fundamentally, the CPU itself is one big loop. The fetch-decode-execute cycle runs continuously—fetch the next instruction, decode it, execute it, repeat. This is the lowest-level loop, and everything else is built on top of it.

The operating system sits on this loop and provides hooks for running programs. Process scheduling determines which program gets CPU time. Interrupts let hardware signal events. System calls let programs request priviledged operations. You write a program; the OS decides when it runs, handles context switching, and manages resources.

Understanding this concept is integral to game engine design, but it extends beyond games. Web servers, browsers, and operating systems are all built on the same abstraction. The underlying idea is the same across domains. A loop runs continuously, processing input and dispatching to handlers. Whether it is a game loop processing player input and updating physics, or a web server processing HTTP requests and invoking controllers, the abstraction is identical. The framework owns the loop, you provide the logic. A loop runs, handlers hook in, complexity is hidden.

When you write a Unity script, a Spring Web controller, or a browser event listener, you are hooking into someone else’s loop. Recognizing this clarifies what the framework does for you and what it expects from you. It also reveals why certain constraints exist: why physics and rendering are best decoupled, why input must be queued between ticks, why falling behind on physics can spiral into a freeze, why games have minimum CPU specs, why required CPU and required GPU are adjacent, why web servers have thread pool limits, why connection timeouts exist, why async I/O matters in high-throughput servers, why requestAnimationFrame is preferred over setInterval for animation.

The game loop is not just a game development concept. It is a systems design pattern, repeated at every layer of the stack.

Footnotes

[1] The example is greatly simplified. Production game loops handle additional concerns.

For physics, we implement interpolation between physics states to smooth rendering when fps exceeds tick rate; capping the number of physics steps per frame to prevent a “death spiral” where falling behind causes more ticks, which causes falling further behind. The accumulator pattern itself has variations—semi-fixed timestep, for instance, runs physics at a variable rate but caps the maximum delta to prevent instability.

For rendering, the render function abstracts away GPU pipelines, draw calls, and shaders; double or triple buffering prevents screen tearing by swapping between front and back buffers; the renderer may operate on an interpolated or snapshot copy of game state rather than the live state; frame pacing and vsync synchronize frame presentation with the display’s refresh rate.

[2] setInterval is not precise—if the event loop is blocked by long-running JavaScript, the callback is delayed. Timers in JavaScript are best-effort, not real-time guarantees. See Reasons for delays longer than specified.