Patterns in Game Code: The Practical Map
- Patterns
- Architecture
- Game Design
- Code Quality
- ECS
Ask 10 game devs about patterns and you'll get 10 different lists. Half think Singleton is fine. Half think Singleton is the devil. Both are right depending on context.
This is the map I wish I had at year one. Architecture patterns, design patterns, game-specific patterns — what each one is, when to use it, when to skip it.
I'll keep each section short. The point isn't to teach the pattern in depth — it's to know which tool to reach for, then study the one you actually need.
Why Patterns Matter (More in Games Than Most Code)
Patterns are shortcuts for thinking. You stop solving "how do I structure this?" every time and start solving "which structure fits?"
In games, this matters more than in most software because:
- Game systems mutate fast. Mechanics change weekly. A clean pattern absorbs change; a tangled one breaks.
- Performance is non-negotiable. Most games run on a 16ms frame budget. Patterns that ignore data layout and allocations cost real fps.
- Teams are small but the code is large. One dev often touches everything. Without patterns, the "everything" becomes unmanageable around month 6.
- Debugging is half the job. A predictable pattern makes the bug reachable. A clever-but-unique solution makes it invisible.
The trap is the opposite extreme: pattern-pilling. Wrapping every class in a factory, every callback in an event bus, every state in a state machine. The right pattern is the simplest one that absorbs the changes you actually expect.
Architecture Patterns (How the Whole Project Is Shaped)
MVC / MVP / MVVM
Separates data (Model), presentation (View), and flow (Controller/Presenter/ViewModel). UI-heavy systems benefit; gameplay loops usually don't.
Use when: Building UI screens, settings menus, level editors. Skip when: Core gameplay loop — adds layers without payoff.
Layered Architecture
Code is split into horizontal layers (UI → Game Logic → Domain → Data). Each layer only talks to the one below.
Use when: Larger projects with clear infra/gameplay separation. Skip when: Prototype or jam. Layering before need is over-engineering.
Onion / Clean / Hexagonal Architecture
The domain (your rules, mechanics, state) sits in the center. Infrastructure (Unity, networking, storage) wraps around it. Dependencies point inward only — Unity depends on your game, not the other way.
Why it's powerful in games: When the engine layer can be swapped (Unity → Unreal, or Unity 2022 → Unity 6), your gameplay survives. When you need to test mechanics without spinning up a scene, you can.
Use when: Long-lived projects, testable mechanics, multi-platform plans. Skip when: Prototype-stage code or hyper-casual.
ECS (Entity Component System)
Entities are IDs. Components are data. Systems are pure functions over collections of components. No OOP inheritance.
Why games care: Cache-coherent memory layout = massive performance for many entities. Unity DOTS, Bevy, custom ECSes are all built on this.
Use when: Crowd sims, large simulations, RTS-style games. Skip when: UI, narrative-driven games, casual mechanics with <50 entities.
Service Locator
A central registry where systems find each other (ServiceLocator.Get<IAudio>()). The "less religious cousin" of dependency injection.
Use when: Mid-size projects without a DI framework. Skip when: Tiny projects (just use direct refs) or large ones (use DI properly).
Creational Patterns (How Objects Get Born)
Factory / Abstract Factory
A function or class that decides which concrete type to instantiate. Caller doesn't care about the type, only the contract.
Use when: Spawning enemies, weapons, items by type ID. Loading from data tables. Skip when: You only have two types and they'll stay that way.
Builder
Constructs complex objects step by step. Fluent APIs (new LevelBuilder().withTiles().withEnemies().build()).
Use when: Level configs, character setups, anything with many optional fields. Skip when: Simple constructors do the job.
Singleton
One instance, globally accessible. Notorious in games for hiding dependencies and breaking testability.
Use when: Truly one-of-a-kind systems with no test surface (audio engine root, native bindings). Skip when: Anything you might mock, swap, or want multiple of (most things).
Object Pool
Pre-allocate N objects, recycle them instead of new/destroy. The single biggest perf pattern in games.
Use when: Bullets, particles, enemies, UI cards, anything spawned >5 times per frame. Skip when: One-off objects (a player, a boss).
Prototype
Clone an existing object instead of building from scratch. Useful when construction is expensive.
Use when: Spawning enemies from a "template" prefab. Skip when: Plain construction is cheap enough.
Structural Patterns (How Objects Connect)
Adapter
Wraps an interface to make it match a different one. Lets old code talk to new code.
Use when: Integrating third-party SDKs that don't match your conventions. Skip when: You control both sides — just refactor.
Decorator
Wraps an object to add behavior without modifying it. Power-ups, buffs, modifiers.
Use when: Stacked effects on an entity (poison + slow + bleed). Skip when: A simple flag or list will do.
Facade
A single, simplified interface over a tangled subsystem. AudioFacade.Play("explosion") hides 5 layers.
Use when: Subsystems other code shouldn't see the guts of. Skip when: The "tangle" is just two methods.
Composite
Treat groups and individuals uniformly via a shared interface. Scene graphs, UI trees.
Use when: Hierarchical structures where you recurse. Skip when: Flat collections.
Flyweight
Share data between many objects to save memory. The classic example: one tile texture, 10,000 tiles.
Use when: Massive numbers of mostly-identical entities. Skip when: Memory isn't your bottleneck.
Behavioral Patterns (How Objects Talk)
Observer / Event System
One object publishes; many subscribe. Decouples publisher from subscriber.
Use when: UI reacts to gameplay (HP changes → bar updates), achievements, analytics. Skip when: A direct call is clearer (don't event-ify everything).
Command
Wrap an action in an object. Enables undo, replay, network sync, queueing.
Use when: Turn-based games, level editors, replay systems. Skip when: Real-time action — overhead isn't worth it.
State / State Machine
Object behaves differently based on its current state. Idle, Running, Jumping, Falling.
Use when: Anything with discrete behavior modes — players, enemies, UI screens. Skip when: Two states (use a bool).
Strategy
Encapsulate an algorithm so it can be swapped at runtime. Different AI behaviors for the same enemy class.
Use when: "Same shape, different brain" — pathfinding modes, scoring rules. Skip when: A switch statement covers it.
Template Method
Base class defines the skeleton; subclasses fill in steps. Common in framework code.
Use when: A workflow with required steps and a few variable points (level setup → spawn → run → teardown). Skip when: Inheritance fights you more than it helps.
Mediator
A central object coordinates a group, so they don't all wire to each other. UI controllers, dialog managers.
Use when: N×N dependencies are forming. Skip when: A few direct refs are still readable.
Memento
Capture an object's state so it can be restored later. Save/load, undo.
Use when: Save systems, time rewind mechanics. Skip when: Plain serialization is enough.
Chain of Responsibility
Pass a request through a chain until something handles it. Input handling pipelines.
Use when: Modular handlers (input → UI → game → debug, in order). Skip when: A switch covers the cases.
Game-Specific Patterns (From Nystrom's Game Programming Patterns)
Game Loop
The core: process input, update, render, repeat. Variable timestep, fixed timestep, or hybrid.
Use when: Always. The question is which variant.
Update Method
Each entity has an Update(dt) called every frame. Engine-default in Unity (MonoBehaviour.Update).
Use when: Default for entity behavior. Skip when: ECS replaces it.
Component (gameplay-flavored)
Compose entity behavior from parts (Health, Movement, Renderer). Unity's GameObject/Component model.
Use when: OOP-flavored engines (Unity, Godot). Skip when: ECS — different paradigm.
Spatial Partition
Divide world into cells/regions for fast neighbor lookup. Quadtree, octree, grid.
Use when: Collision, AI sight checks, anything with "find things near me" at scale. Skip when: <100 entities.
Dirty Flag
Mark something as needing recompute, defer the work. Transform hierarchies, UI layout.
Use when: Expensive calculations triggered by cheap changes. Skip when: Recompute is fast.
Type Object
Define types in data, not code. "Goblin" and "Orc" are instances of EnemyType, not separate classes.
Use when: Designers need to add content without touching code. Skip when: Few types and code-only team.
Event Queue
Decouple "when something happened" from "when it's processed." Defer, batch, replay.
Use when: Audio, animation triggers, analytics. Skip when: Direct call is timing-correct.
Double Buffer
Two copies of state — read from one, write to the other, swap. Hides partial updates.
Use when: Rendering, simulation steps. Skip when: No partial-state visibility issue.
Data Locality
Lay out memory to match access patterns. Arrays of components beat lists of objects when you process all of them.
Use when: Hot loops over many entities. Skip when: Code clarity matters more than 0.5ms.
How to Pick
Three rules I use:
1. Match the pattern to the change you actually expect. Building Onion architecture for a 2-week game jam is theater. Singleton on your audio system at year three is a nightmare. Pick the simplest pattern that absorbs likely change.
2. Patterns are vocabulary, not religion. A good codebase uses 10-15 patterns lightly, not 3 patterns dogmatically. The skill is knowing the menu.
3. Refactor toward patterns, not from them. Start with the dumbest code that works. When you hit pain, the pattern that solves it is now obvious. Patterns introduced before pain are usually wrong.
Bottom Line
Most senior game devs don't reach for patterns because they memorized them. They reach for them because they've felt the pain that each pattern solves.
Build the vocabulary now, lightly. When the codebase complains, you'll know which word to use.
That's the whole skill.