Event Systems and the Observer Pattern

Updated June 2026
An event system, also called an event bus or an implementation of the observer pattern, lets one part of a game announce that something happened without knowing or caring who will react. Interested parts subscribe to the events they care about and respond independently. This decoupling is what lets a game grow new features without growing a tangle of direct references between its modules.

The Coupling Problem Events Solve

Consider what happens when the player collects a coin. Several things should occur: the score increases, a sound plays, a small particle burst appears, and maybe an achievement tracker checks whether the player has now collected a hundred coins. The direct approach has the coin's collection code call into each of those systems itself: it calls the score manager, the audio manager, the particle system, and the achievement tracker. The coin now depends on four other systems, and every one of those dependencies is a thread of coupling that makes the code harder to change.

This direct-call approach degrades quickly. When you add a fifth reaction, such as a screen flash, you must edit the coin code again. When you want the same reactions for collecting a gem, you duplicate the calls. When the audio manager's interface changes, every caller breaks. The coin, which should only care about being collected, has become entangled with the entire game. Multiply this across every interaction in the game and you get the tightly coupled mess that makes large games unmaintainable.

How an Event System Works

An event system inverts the relationship. Instead of the coin calling the systems that react, the coin simply announces what happened by emitting an event, for example coinCollected, possibly with some data like the coin's position and value. It does this through a shared event bus, and then it is done. It does not know or care whether anything is listening.

The reacting systems take the initiative instead. At startup, the score manager subscribes to coinCollected and increments the score when it fires. The audio manager subscribes and plays a sound. The particle system subscribes and spawns a burst. Each one registers a listener function with the event bus, and when the event is emitted, the bus calls every registered listener in turn. The coin and the reacting systems never reference each other directly. They only share knowledge of the event name and its data shape.

The mechanism itself is simple. An event bus is essentially a map from event names to lists of listener functions. Emitting an event looks up the list for that name and calls each function with the event data. Subscribing adds a function to the list, and unsubscribing removes it. This small piece of infrastructure, often only a few dozen lines, is enough to decouple an entire game's interactions. The pattern is the same one the browser itself uses for DOM events with addEventListener, applied to game-specific events.

Key Takeaway

An event system replaces direct calls between modules with announce-and-subscribe. The emitter knows nothing about its listeners, so you can add or remove reactions without ever touching the code that triggers them.

What Decoupling Buys You

The practical payoff is that features become additive rather than invasive. Adding a screen flash when a coin is collected means writing one new listener that subscribes to coinCollected, with no change to the coin or any other system. Removing a feature means deleting its listener. The code that triggers events stays stable while the code that reacts to them grows and shrinks freely. This is the property that lets a small team keep adding to a game without each addition raising the risk of breaking something unrelated.

Events also enable clean coordination between composed parts. In a component-based or entity component architecture, components are deliberately independent and should not reach into each other. An event system gives them a way to cooperate without rebuilding that coupling: a health component emits a died event, and a loot component, an audio component, and a score component each respond, none of them aware of the others. Composition and events are complementary, with composition keeping parts independent and events letting them coordinate when they must.

Events extend naturally to other parts of a game's architecture too. A networked game can serialize certain events and send them across the wire, so that a local action produces the same reactions on a remote client. An analytics layer can subscribe to gameplay events to record what players do. A replay system can log events to reconstruct a session. Because events are already a clean, named description of what happened, they become a useful seam for many features beyond the original reaction handling.

The Pitfalls of Event-Driven Code

Event systems are powerful enough to be overused, and they carry real downsides when applied carelessly. The most common complaint is that control flow becomes hard to trace. When everything communicates through events, understanding what happens after a coin is collected means searching the whole codebase for everything that subscribes to that event, rather than reading a straightforward sequence of calls. The decoupling that helps you change the code can hurt you when you are trying to understand it. Clear event names and a central list of events help, but the tradeoff is genuine.

A second pitfall is ordering and timing. If two listeners both react to the same event and one depends on the other having run first, the implicit ordering of listeners becomes a fragile hidden dependency. Events are best used for reactions that are genuinely independent. When ordering matters, an explicit sequence or a structured update order is clearer than relying on the order listeners happen to be registered. Some teams also queue events to process at a defined point in the frame rather than firing them instantly, which avoids reentrancy bugs where a listener emits another event mid-handling.

Finally, forgetting to unsubscribe causes leaks and ghost reactions. If a destroyed object's listener stays registered, it keeps the object alive in memory and may run logic on something that no longer exists. Whenever an object that subscribes to events is removed, it must unsubscribe. Used with discipline, an event system is one of the most valuable decoupling tools in game architecture. Used without it, it becomes an invisible web that is as hard to follow as the direct coupling it replaced. The skill is reserving events for independent, broadcast-style reactions and keeping tightly ordered logic explicit.

Immediate Versus Queued Dispatch

An event system can deliver events in two fundamentally different ways, and the choice affects both correctness and how easy the system is to reason about. Immediate dispatch calls every listener synchronously the instant an event is emitted, so by the time the emitting code's next line runs, all reactions have already happened. Queued dispatch instead places the event in a queue and processes the whole queue at a defined point in the frame, so reactions happen later, at a moment you control.

Immediate dispatch is simpler and is what most small games use. Its danger is reentrancy. If a listener reacting to one event emits another event, which triggers more listeners that emit still more, you can end up deep in a chain of nested dispatches, with state changing underneath code that is still running. A particularly nasty version is a listener that modifies the very list of listeners it is being called from, such as an object unsubscribing during its own handler, which can corrupt the iteration if the system is not written defensively. These bugs are subtle and frame-dependent, which makes them hard to reproduce.

Queued dispatch avoids most of these problems by decoupling emission from handling in time. Events emitted during the update phase can all be processed at the end of the phase, in a single pass, with no nesting and no surprise state changes mid-handler. This makes the order of reactions predictable and eliminates reentrancy, at the cost of reactions happening slightly later than the event and a little more infrastructure. Many games use a hybrid, dispatching most events immediately for simplicity while queueing the few that are prone to chaining. Knowing which model your event system uses is essential, because the two have genuinely different behavior in the corner cases that cause the hardest bugs.