Skip to content

Game Programming Patterns

A Game Design Document describes what the game is. This page describes how the code is shaped to support it. The patterns below are the common architectural building blocks of game engines and gameplay code: ways to decouple systems, sequence frame work, model behavior, and squeeze out performance.

These are own-words summaries focused on intent, when to reach for it, and the trade-off you accept. They are not a substitute for the source material.


Classic Gang-of-Four patterns, reconsidered through a game-shaped lens — where they help and where they hurt.

Wrap an action in an object so it can be stored, queued, undone, or replayed. The caller fires a command without knowing what it does; the command knows how to do and undo it.

  • Use when: input remapping, undo/redo, AI issuing the same actions as a player, recording/replaying input, networked input streams.
  • Trade-off: one object per action adds indirection and allocation; pool or flyweight commands if they are hot.

Share the data that is identical across many instances; keep only the per-instance data local. A thousand trees reference one shared mesh/texture and carry only their own transform.

  • Use when: huge counts of near-identical entities (foliage, tiles, particles, mobs).
  • Trade-off: the shared state must be immutable; mutation leaks across every user of the flyweight.

Let objects announce events without knowing who listens. Subjects publish; observers subscribe. Decouples the thing that changes from the things that react.

  • Use when: achievements, UI reacting to gameplay, analytics, loosely coupled cross-system notifications.
  • Trade-off: implicit control flow is hard to trace; dangling observers leak. Prefer an explicit event queue for high-volume or ordered events.

Create new objects by cloning an existing instance rather than instantiating a class. Spawners hold a template and stamp out copies.

  • Use when: data-driven spawning, editor-defined entity templates, varied enemy definitions without a class explosion.
  • Trade-off: deep vs shallow clone bugs; in practice a data/Type Object approach is often clearer than literal cloning.

A single globally-accessible instance. The most over-used pattern in game code.

  • Use when: rarely — and even then prefer dependency injection or a Service Locator.
  • Trade-off: hidden coupling, ordering hazards at startup/shutdown, and a wall for testability and multithreading. Treat as a smell, not a default.

Patterns that govern time — how the game advances frame to frame.

Render or compute into a back buffer, then swap it with the front buffer atomically. Readers always see a complete, consistent frame; writers never tear.

  • Use when: rendering, any state where a half-updated read is visible/wrong (e.g. cellular automata, simultaneous-turn resolution).
  • Trade-off: double the memory for the buffered state; the swap must be cheap and atomic.

The heartbeat: process input, update the world, render — forever, decoupled from the wall clock so the game runs the same on fast and slow hardware.

  • Use when: always; it is the spine of real-time games. Choose fixed-timestep update with interpolated rendering for deterministic simulation.
  • Trade-off: a variable timestep is simpler but makes physics/sim non-deterministic and order-sensitive.

Each live entity gets an update() called once per frame to advance its own behavior by one tick. The loop walks a collection and ticks each.

  • Use when: many objects with independent per-frame behavior.
  • Trade-off: scattered cache access and per-object virtual calls hurt at scale — see Data Locality and ECS designs as the answer.

Patterns for defining what entities do.

Encode behavior as data — instructions for a tiny virtual machine — instead of hardcoding it in the engine language. Designers/modders author behavior without recompiling.

  • Use when: spell/skill scripting, modding surfaces, sandboxed user content, data-driven content at volume.
  • Trade-off: you are building (and debugging) a VM and toolchain; heavy upfront cost. AngelScript/Lua often fill this role off-the-shelf.

A base class exposes a set of safe primitive operations; subclasses define behavior by combining those primitives, never touching the engine directly.

  • Use when: many variations of one concept (power-ups, abilities) authored by gameplay programmers.
  • Trade-off: the base class becomes a god-object of primitives; favor composition (Component) when variation explodes.

Move what would be subclasses into data instances of a single “type” class. A Monster holds a reference to a MonsterType (“Dragon”, “Goblin”) that carries shared stats/behavior.

  • Use when: content defined by designers in data; new “kinds” should not require new code.
  • Trade-off: less compile-time type safety; behavior differences beyond data still need code.

Model an entity as a finite state machine — explicit states with explicit transitions — instead of a tangle of boolean flags.

  • Use when: characters with modes (idle/walk/jump/attack), UI flows, AI. Use hierarchical/pushdown FSMs when states share sub-behavior.
  • Trade-off: state explosion for complex behavior; consider behavior trees or StateTree for richer AI.

Patterns that let one part of the program change without breaking others.

Build an entity by composing independent components (position, render, physics, health) instead of one deep inheritance tree. The entity is a bag of parts.

  • Use when: entities that mix and match capabilities; the foundation of ECS.
  • Trade-off: cross-component communication needs a discipline (messages, shared data, or systems) or it becomes spaghetti.

Decouple when an event is sent from when it is processed. Producers enqueue; a consumer drains the queue on its own schedule.

  • Use when: audio, async systems, smoothing bursty input, ordering and rate-limiting cross-system messages.
  • Trade-off: added latency; queued state can go stale; debugging is harder than a direct call. Watch for feedback loops.

Provide global access to a service through an indirection layer, so callers find it without hardcoding the concrete implementation.

  • Use when: swappable subsystems (audio, logging, platform layer), null/mock implementations for tests.
  • Trade-off: still a global; hides dependencies. Prefer explicit injection where practical.

Patterns that trade complexity for speed — apply only after profiling.

Lay out data so the CPU cache stays hot: contiguous arrays of the data a loop actually touches, processed in order. The single biggest real-world game perf lever.

  • Use when: hot loops over many entities; the core motivation behind ECS / struct-of-arrays.
  • Trade-off: less flexible, less object-oriented layout; harder to mutate structure at runtime.

Defer expensive recomputation until the result is actually needed, and skip it entirely when nothing changed. Mark dirty on write; recompute lazily on read.

  • Use when: derived data that is costly to compute and changes less often than it is read (world transforms, baked lighting, derived stats).
  • Trade-off: correctness hinges on every mutation setting the flag; a missed set is a silent stale-data bug.

Reuse a fixed set of pre-allocated objects instead of allocating/freeing constantly. Take from the pool, return when done.

  • Use when: high-churn short-lived objects (bullets, particles, effects, network packets).
  • Trade-off: must reset object state on reuse; pool sizing and leaks; objects outlive their logical lifetime.

Organize objects by their position (grid, quadtree, BVH, BSP) so spatial queries touch only nearby candidates instead of every object.

  • Use when: collision, range queries, neighbor finding, culling at scale.
  • Trade-off: the structure must be maintained as objects move; choose grid vs tree by density and movement patterns.

ECS = Data Locality + Component

rareicon (Unity DOTS) batches systems over packed component arrays — Component + Data Locality + Update Method, fused.

State / StateTree

behavior_statetree models AI/units as typed-command state machines with a bounded queue boundary.

Object Pool

Combat events, bullets, and network packets reuse pooled buffers rather than per-frame allocation.

Event Queue

Cross-system messaging (chat relay, combat pipeline) drains queues on a fixed cadence rather than direct calls.


  • Game Programming Patterns — Robert Nystrom, free online: gameprogrammingpatterns.com
  • Design Patterns: Elements of Reusable Object-Oriented Software — the Gang of Four (origin of Command, Flyweight, Observer, Prototype, Singleton, State).
  • Unity DOTS / ECS docs — Data Locality and Component patterns in a production engine.