Skip to content
Techniques & Technology

The Game Loop

The heartbeat of every game

Every game runs on the same fundamental pattern: read input, update state, render graphics, repeat. Understanding the game loop is the first step to building anything interactive.

commodore-64sinclair-zx-spectrumcommodore-amiganintendo-entertainment-systemprogrammingarchitecturefundamentals1962–present

The game loop is the core rhythm that drives every video game. Each frame, the game reads player input, updates the game world, draws the result, and repeats. This simple pattern—running 50 or 60 times per second—creates the illusion of continuous motion and responsiveness.

The basic pattern

initialise game state

repeat forever:
    read input
    update game state
    render graphics
    wait for next frame

This structure appears in every game from Pong to modern AAA titles.

Frame timing

Fixed frame rate

Most 8-bit games synchronise to the display refresh:

System Refresh rate Frame time
PAL 50 Hz 20 ms
NTSC 60 Hz 16.67 ms

Synchronising to vertical blank (VBlank) ensures:

  • Consistent game speed.
  • Tear-free graphics updates.
  • Predictable timing for music.

Variable timestep

Modern games often use:

delta_time = current_time - last_frame_time
update(delta_time)

But 8-bit games typically assume fixed timing.

Implementation approaches

Busy-wait loop

The simplest pattern — poll the raster register until it crosses a known scanline:

main_loop:
    jsr read_input
    jsr update_game
    jsr render_graphics

wait_vsync:
    lda $d011         ; VIC-II control reg; bit 7 = high bit of raster line
    bpl wait_vsync    ; loop while raster < 256 (visible area on PAL is 0-249)

    jmp main_loop

The $D011 bit-7 check waits for the raster to enter the upper border / vblank region (raster ≥ 256 on PAL = lines 256-311). Simple but wastes cycles during the wait.

Interrupt-driven

The C64 design Cadaver describes as tried and true keeps the raster interrupts small and the main loop free-running. The interrupts do only what has to land on a particular raster line — “the immediate setting of VIC registers (like screen-splits and sprite multiplexing) and playing music/sound” — and “all the time-consuming things like movement, AI & scrolling are done in the main program”. The two halves meet through a frame counter:

; Raster IRQ at the bottom of the screen: VIC writes and music only
irq_handler:
    ; ... set registers for the next frame, play music ...
    inc frame_count
    dec $d019         ; acknowledge
    rti

; Main program: logic, then wait for the next frame
main_loop:
    jsr read_input
    jsr update_game
    jsr render_graphics
    lda frame_count
.wait:
    cmp frame_count
    beq .wait
    jmp main_loop

The payoff is graceful failure. If the main loop overruns, the interrupts still fire on time and the screen still shows correctly; the game slows down as a whole instead of tearing or flickering. Richard Bayliss’s guide puts the same thing in fewer words: the routines “that will synchronize your game code and loop the game engine continuously” come first, and “Most C64 games also use IRQ raster interrupts to perform various tasks.”

Phase structure

1. Input phase

Read all input sources at the start:

  • Joystick positions
  • Keyboard state
  • Network data (for multiplayer)

Store values for consistent use throughout the frame.

2. Update phase

Process game logic in order:

  1. Player movement and actions
  2. Enemy AI and movement
  3. Physics and collisions
  4. Scoring and game state
  5. Sound triggers

3. Render phase

Draw everything to screen:

  1. Clear or scroll background
  2. Draw static elements
  3. Draw moving objects (sprites)
  4. Update score display

Critical: On systems without double-buffering, update graphics during VBlank to avoid tearing.

Timing considerations

VBlank window

System VBlank cycles
C64 PAL ~7,000 cycles (112 border lines × 63; no badlines outside the display window)
NES ~2,270 cycles
ZX Spectrum varies by border

Graphics updates must complete within this window.

Splitting work

If update takes too long:

  • Spread AI across multiple frames
  • Update only visible sprites
  • Use dirty rectangle rendering
  • Run the logic every second or fourth frame and interpolate sprite positions in between, as Metal Warrior 4 does — see Frameskipping and Interpolation. Gauntlet III and Myth scroll at 50 Hz while moving their sprites at a lower rate.

State machines within the loop

game_loop:
    lda game_state
    cmp #STATE_TITLE
    beq handle_title
    cmp #STATE_PLAYING
    beq handle_playing
    cmp #STATE_GAMEOVER
    beq handle_gameover

Different states run different logic while sharing the same loop structure.

Common mistakes

Mistake Problem Solution
No frame sync Game speed varies Wait for VBlank
Render during display Visual tearing Update in VBlank only
Too much per frame Slowdown Spread work across frames
Input once per object Inconsistent response Read once, use everywhere

Platform-specific notes

Commodore 64

Use raster interrupts or poll $D012 for VBlank. The common shape is a bottom-of-screen interrupt that writes the VIC registers and plays the music, with everything else in the main loop, synchronised by a frame counter.

ZX Spectrum

Check FRAMES system variable or use HALT to wait for interrupt.

NES

NMI fires at VBlank start—natural synchronisation point.

Amiga

Copper can trigger interrupts; also use VBlank interrupt.

See also

Not yet fact-checked. This entry was drafted by an AI and nobody has verified it. The dates, figures and technical details may be wrong. Use it to find your bearings, then confirm anything that matters against a primary source.