Skip to main content

Game Loop

complexitypopularity

Run a loop that continuously reads input, advances state, and renders output at a controlled cadence for the entire active lifetime of a program - the opposite of waiting idle for the next request.

The problem

Game.onKeyPress() moves the player and redraws. It looks complete until anything needs to move on its own: a thrown projectile, a patrolling enemy, a countdown timer. None of those have a key to press. Between two key presses, time effectively stands still - nothing in the world advances unless the player caused it directly.

Request/response thinking (wait for input, react, wait again) has no room for "keep happening regardless of input." A world with physics, AI, or timers needs to advance on its own schedule, not the player's.

The solution

Wrap the whole active lifetime of the program in one loop that repeats a fixed sequence: read whatever input arrived, advance every piece of state by however much time actually passed, then draw the result. Nothing in that sequence requires a key press - update() runs every tick, whether or not processInput() found anything to drain.

The loop itself stays deliberately dumb: it does not know what a player or an enemy is, it just calls three methods in order, on a schedule. All actual game logic lives in the object it drives, which is what keeps the loop reusable across entirely different games.

ApplicationGameLoopGameWorldRender surfacerun()1frameStart = clock.now()2processInput()3update(deltaTime)4render(surface)5sleep(targetFrameTime - elapsed)6while (running) repeat7
  1. 1Once the game starts, control never returns to normal request/response code until the game ends.
  2. 2Every iteration starts by measuring time, because how much state advances depends on how long the last tick actually took.
  3. 3The world drains whatever input arrived since the last tick - a key press does not get its own separate event handler.
  4. 4State advances by exactly as much time as really passed, so gameplay speed does not depend on hardware speed.
  5. 5Only after state is fully advanced does anything get drawn - never mid-update, or the frame would show a half-updated world.
  6. 6If the tick finished early, the loop waits out the remainder instead of burning the CPU redrawing the same frame needlessly.
  7. 7The whole sequence repeats, tens of times a second, for as long as the game is active.

Structure

GameLoop has no relationship to InputSource at all in this diagram - only GameWorld does. The loop drives the cadence; the world decides what the cadence means.

«interface»InputSourcepoll()INPUTGameLooprunning: booleanrun()stop()DRIVERGameWorldprocessInput(input)update(deltaTime)render(surface)STATEClocknow()TIME
uses

Code

Same example three ways: a loop driving a world with a player and patrolling enemies.

// The game only ever reacts when something happens - nothing moves on its own.
class Game is
method onKeyPress(key) is
player.move(key)
redraw()
// Nobody called anything, so a projectile in flight never advances,
// an enemy never patrols, and time itself effectively stands still
// between key presses.
// The loop owns cadence only; the world owns every rule about what happens each tick.
class GameLoop {
run() {
this.running = true;
while (this.running) {
const frameStart = this.clock.now();
this.world.processInput();
this.world.update(this.clock.deltaSinceLastTick());
this.world.render(screen);
this.waitForNextTick(frameStart);
}
}
}
// A projectile, an enemy patrol, a countdown timer - all advance every tick,
// whether or not a key was pressed this frame.
// The driver. It knows nothing about the game itself - just the cadence.
class GameLoop is
field world: GameWorld
field clock: Clock
field running: boolean
field targetFrameTime: 16 // ~60 updates per second
 
method run() is
running = true
while running do
frameStart = clock.now()
 
world.processInput()
world.update(frameStart.deltaSinceLastTick())
world.render(screen)
 
elapsed = clock.now() - frameStart
if elapsed < targetFrameTime then
sleep(targetFrameTime - elapsed)
 
method stop() is
running = false
 
// All game state and rules live here, untouched by loop mechanics.
class GameWorld is
field player: Player
field enemies: list of Enemy
field pendingInputs: queue
 
method processInput() is
foreach (event in inputSource.poll()) do
pendingInputs.enqueue(event)
while not pendingInputs.isEmpty() do
applyInput(pendingInputs.dequeue())
 
method update(deltaTime) is
player.update(deltaTime)
foreach (enemy in enemies) do
enemy.update(deltaTime)
 
method render(surface) is
surface.clear()
player.draw(surface)
foreach (enemy in enemies) do
enemy.draw(surface)
surface.present()

When to use it

  • State needs to advance continuously - physics, AI, timers, animation - independent of whether the user did anything this instant.
  • The program has one clear "active" phase (a game in progress, a live simulation) where driving forward at a controlled cadence matters more than minimizing idle CPU use.

Pitfalls

  • Physics tied to frame rate. Advancing state by a fixed amount per frame instead of by real elapsed time makes gameplay run faster on faster hardware - deltaTime exists specifically to prevent this.
  • Unbounded variable timestep. If a slow frame produces a huge deltaTime in one jump (say, after the OS paused the process), physics can tunnel through walls or behave wildly. Clamping the maximum deltaTime per tick is the usual guard.
  • Burning CPU for no reason. A loop with no sleep/wait at the end of a fast tick will spin at 100% CPU rendering identical frames. The wait step is not an optimization - it is part of a correctly behaved loop.

Don't confuse it with

  • Template Method. The loop's own run() is frequently implemented as a template method - a fixed skeleton calling overridable processInput/update/render steps - but Game Loop names the runtime cadence pattern; Template Method names the class-structuring technique one implementation of it happens to use.
  • Observer / event-driven architecture. Event-driven code does nothing until an event fires. Game Loop advances unconditionally every tick. The two commonly coexist: input events get queued by an Observer-style listener and drained once per tick inside processInput().
  • A simple while polling loop. Polling for one condition until it becomes true is not this pattern by itself - Game Loop specifically names the three-phase cadence (input, update, render) sustained for the program's whole active lifetime, not an arbitrary loop that happens to repeat.

Check yourself

Question 1 of 5

What is the correct order of the three phases inside one tick of a game loop?