Entity Component System (ECS) Explained
The Three Parts of an ECS
The name describes the architecture exactly. There are entities, there are components, and there are systems, and each one has a single clear job. The power of the pattern comes from how strictly these roles are kept apart.
An entity is nothing more than an identifier, usually an integer. It has no data and no behavior of its own. An entity with ID 42 is not a player or an enemy or a bullet in any inherent sense. It only becomes something by the components attached to it. Think of the entity as a key that ties together a set of components, not as an object in the traditional sense.
A component is a plain data container with no logic. A Position component holds x and y. A Velocity component holds dx and dy. A Sprite component holds an image reference. A Health component holds current and maximum hit points. Components never contain methods that do work. They are pure state, and that purity is what makes the rest of the architecture possible.
A system contains the logic and operates on entities that have a particular set of components. A movement system runs each frame over every entity that has both a Position and a Velocity, adding the velocity to the position. A render system runs over every entity with a Position and a Sprite, drawing each one. A damage system runs over entities with Health. Systems are where all the behavior lives, and each system cares only about the components relevant to its job, ignoring everything else an entity might have.
How a Frame Runs in an ECS
A frame in an ECS game is a clear, ordered sequence of systems. The game loop calls each system in turn, and each system queries for the entities it cares about and processes them. There is no scattered network of objects calling each other's methods. Instead, the frame reads like a recipe: run the input system, then the AI system, then the movement system, then the collision system, then the render system.
This linear flow is one of the underrated benefits of ECS. In an object-oriented game, understanding what happens in a frame means tracing method calls across many classes, because any object's update method might call into any other object. In an ECS, the order of operations is the explicit list of systems, and the data each system reads and writes is declared by the components it queries. The behavior of the whole game becomes far easier to follow and to reason about.
Querying is the mechanism that connects systems to entities. Each system asks the world for all entities that have a specific combination of components, and the world returns just those. An entity with Position, Velocity, Sprite, and Health is automatically picked up by the movement system, the render system, and the damage system, with no registration code, simply because it has the components each of those systems requires.
Why ECS Scales So Well
ECS is popular for a practical reason beyond elegance: it tends to be fast, especially at high entity counts. The reason is memory layout. Because components are pure data with no behavior, an ECS can store all the Position components together in one tight array, all the Velocity components in another, and so on. When the movement system runs, it walks two contiguous arrays in lockstep, which is exactly the access pattern modern CPUs are built to make fast through caching and prefetching.
Contrast this with the object-oriented layout, where each game object is a separate heap allocation holding all of its data and scattered randomly in memory. Iterating over those objects means jumping around memory, causing frequent cache misses that stall the CPU. The performance gap between cache-friendly contiguous data and scattered objects grows with the number of entities, which is why simulations and games with very high object counts gravitate toward ECS. This data-oriented approach is the core idea behind why ECS earned its reputation for performance.
On the web, the benefit is real but worth keeping in perspective. JavaScript engines do not give you the same control over memory layout that a language like Rust or C++ does, so a JavaScript ECS captures part of the cache benefit rather than all of it. The bigger win for most web games is the architectural one: composition, clear data flow, and the ability to add new kinds of objects without modifying existing systems.
ECS separates what an object has, its components, from what acts on it, its systems. That separation is what makes new object types free to create and what lets the data sit in cache-friendly arrays.
Composition in Action
The clearest way to feel the value of ECS is to see how it handles a new kind of object. Suppose your game has players and enemies, and now you want a flying turret that shoots like an enemy but cannot move and cannot be approached on foot. In a class hierarchy, this awkward combination forces an uncomfortable decision about where it belongs in the tree. In an ECS, you simply attach the components it needs: Position, Sprite, Health, a Weapon component, and an AI targeting component, while leaving off the Velocity component since it does not move. The movement system ignores it automatically because it has no Velocity. No system needs to change. The new object is just a new combination of existing parts.
This is the same composition principle that underlies the broader move away from inheritance, taken to its logical extreme. Every gameplay feature becomes a component plus the system that processes it. Want destructible terrain? Add a Health component to terrain tiles and the existing damage system handles them. Want a poison effect? Add a Poison component and a system that drains Health from anything carrying it. Features compose cleanly because they are independent pieces rather than entangled methods on a class.
The Costs and When to Use ECS
ECS is not the right choice for every game, and pretending otherwise leads to over-engineered small projects. The pattern carries real costs. It is more abstract than objects with methods, which raises the barrier for newcomers to the codebase. The indirection of querying entities by component can feel like ceremony in a simple game. Debugging shifts from following an object's method calls to thinking about data flowing through systems, which is a different mental model that takes adjustment. And some genuinely object-shaped logic, like a complex one-off boss, can feel forced into the data-and-systems mold.
The pattern earns its cost when a game has many entities that interact in varied combinations. Bullet-hell shooters, simulations, strategy games, survival games, and anything with emergent interactions between large numbers of objects are natural fits. For these, ECS keeps the code scalable in a way that class hierarchies cannot match. For a linear narrative game, a simple puzzle, or a small arcade title, plain objects or lightweight composition are usually the better, simpler choice.
A reasonable middle path exists. You do not need a heavyweight ECS framework to benefit from the ideas. Many web games use a lightweight approach where entities are plain objects holding a map of component data, and update functions iterate over entities that have the components they care about. This captures most of the architectural benefit, composition and clear data flow, without committing to a full framework or the strictest data-oriented memory layout. Start there, and adopt a full ECS only when entity counts and complexity genuinely demand the extra performance and rigor.
A Lightweight ECS in Plain JavaScript
You can build a usable ECS in plain JavaScript without any library, which is the best way to understand the pattern before deciding whether you need a heavier framework. The world holds a counter that hands out unique entity IDs and a set of stores, one per component type, where each store maps an entity ID to that entity's component data. Creating an entity is incrementing the counter, and attaching a component is adding an entry to that component's store under the entity's ID. Querying for all entities with a given set of components means finding the IDs present in every relevant store, which the systems iterate over each frame.
This minimal version captures the architectural essence, composition, separation of data and logic, and clear per-frame data flow, in a small amount of code you fully understand and control. It will not match the raw performance of a framework engineered for cache-friendly memory layout, but for a great many web games it is more than fast enough, and it carries none of the learning curve or dependency weight of a third-party system. Several published browser games run on exactly this kind of hand-rolled ECS.
The time to graduate to a dedicated ECS library is when entity counts climb into the tens of thousands and the query and iteration overhead of the simple version starts showing up in the profiler, or when you want features like fast archetype-based queries that are tedious to build yourself. Until then, the lightweight approach lets you adopt ECS thinking incrementally, learn where it helps your specific game, and avoid committing to a framework before you know you need one. Start simple, measure, and reach for more machinery only when the game demands it, the same principle that governs every other architectural decision.