Understanding Entity Component Systems (ECS): A Beginner-Friendly Guide
What Is an Entity Component System?
An Entity Component System, usually shortened to ECS, is an architectural pattern commonly used in games, simulations, and other systems that need to manage large numbers of objects with different behaviours.
At first, ECS can look more complicated than traditional object-oriented programming.
You may be used to creating classes such as:
1class Player {2 constructor() {3 this.health = 1004 this.position = { x: 0, y: 0, z: 0 }5 this.speed = 56 }7
8 move() {9 // movement logic10 }11
12 takeDamage(amount) {13 // damage logic14 }15}Everything related to the player lives inside the Player class.
ECS takes a very different approach.
Instead of building increasingly large objects containing both data and behaviour, ECS separates the game into three main concepts:
- Entities
- Components
- Systems
The easiest way to understand ECS is to look at each of those separately.
1. Entities Are Just Identities
An entity represents something that exists in the game.
Examples might include:
- Player
- Enemy
- Sword
- Health potion
- Tree
- Door
- Projectile
- Chest
- NPC
The important part is that the entity itself usually contains very little information.
In a simple ECS, an entity might literally just be an ID:
1const player = 12const enemy = 23const sword = 3Or your ECS may generate them automatically:
1const player = world.createEntity()The entity doesn't necessarily know that it's a player.
It becomes a player because of the components attached to it.
2. Components Contain Data
Components describe what an entity has.
For example, we might create a Transform component:
1class Transform {2 constructor(x = 0, y = 0, z = 0) {3 this.x = x4 this.y = y5 this.z = z6 }7}A Health component:
1class Health {2 constructor(current = 100, max = 100) {3 this.current = current4 this.max = max5 }6}And a Velocity component:
1class Velocity {2 constructor(x = 0, y = 0, z = 0) {3 this.x = x4 this.y = y5 this.z = z6 }7}Notice something important.
These components contain data, but they don't contain game logic.
Health doesn't have:
1health.takeDamage()Velocity doesn't have:
1velocity.move()Instead, systems perform those operations.
This separation is one of the main ideas behind ECS.
3. Systems Contain Logic
Systems operate on entities that have the components they require.
Imagine that an entity has both:
- Transform
- Velocity
A movement system can find those entities and update their positions.
Conceptually:
1class MovementSystem {2 update(world, deltaTime) {3 const entities = world.query(Transform, Velocity)4
5 for (const entity of entities) {6 const transform = world.getComponent(entity, Transform)7 const velocity = world.getComponent(entity, Velocity)8
9 transform.x += velocity.x * deltaTime10 transform.y += velocity.y * deltaTime11 transform.z += velocity.z * deltaTime12 }13 }14}The MovementSystem doesn't care whether the entity is:
- a player
- an enemy
- a projectile
- a moving platform
If it has the required components, the system can process it.
This is where ECS starts becoming powerful.
Composition Instead of Inheritance
Traditional game architecture often uses inheritance.
You might start with:
1GameObject2 ↓3Character4 ↓5Enemy6 ↓7FlyingEnemyEventually you can end up with complicated inheritance trees.
What happens when you need an enemy that:
- flies
- takes damage
- can be poisoned
- drops loot
- has an inventory
- can interact with objects
With ECS, you build behaviour by combining components.
For example:
1Enemy Entity2
3Transform4Health5Velocity6EnemyAI7LootTable8StatusEffectsWant it to fly?
Add:
1FlyingWant it to have an inventory?
Add:
1InventoryWant another enemy to be stationary?
Simply don't give it a Velocity component.
Instead of asking:
What class does this object inherit from?
ECS encourages you to ask:
What capabilities does this entity have?
A Simple Player Entity
A player might be constructed from components like this:
1const player = world.createEntity()2
3world.addComponent(player, new Transform(0, 1, 0))4world.addComponent(player, new Velocity())5world.addComponent(player, new Health(100, 100))6world.addComponent(player, new PlayerControlled())The PlayerControlled component can simply act as a marker.
For example:
1class PlayerControlled {}Now a PlayerControllerSystem can query:
1PlayerControlled2Transform3Velocityand know that those entities should respond to player input.
Marker Components
Some components don't need to store any values at all.
These are often called marker components or tag components.
For example:
1class Dead {}When an entity dies, you could add:
1world.addComponent(entity, new Dead())A system can then query entities with the Dead component.
Similarly:
1class PlayerControlled {}2class StaticBody {}3class DynamicBody {}4class Interactable {}The presence of the component itself communicates information about the entity.
Queries Are an Important Part of ECS
Systems need an efficient way to discover which entities they should process.
That's where queries come in.
For example:
1world.query(Transform, Velocity)means:
Give me every entity that currently has both Transform and Velocity.
A damage-related system might query:
1world.query(Health)A player movement system might query:
1world.query(PlayerControlled, Transform, Velocity)A collision system might look for:
1world.query(Transform, Collider)As your game grows, queries become one of the most important parts of your ECS architecture.
Why Keep Components Data-Only?
Technically, you could put methods inside components.
But keeping components focused on data provides some major advantages.
Systems Become Easier to Understand
All movement logic lives inside movement-related systems.
All damage logic lives inside damage-related systems.
You don't need to search through dozens of entity classes to discover where something happens.
Components Become Reusable
A Health component doesn't care whether it belongs to:
- a player
- a boss
- a destructible barrel
- a training dummy
The same data structure can be reused everywhere.
Behaviour Becomes Composable
Adding or removing components can change what an entity is capable of doing.
That makes ECS especially useful for games where objects can gain and lose abilities dynamically.
Example: Damage in an ECS
Imagine an attack deals 25 damage.
Instead of calling:
1enemy.takeDamage(25)you might create a damage request or event:
1eventBus.emit('damage', {2 target: enemy,3 amount: 25,4})A DamageSystem can process the request:
1class DamageSystem {2 update(world) {3 const events = world.events.consume('damage')4
5 for (const event of events) {6 const health = world.getComponent(event.target, Health)7
8 if (!health) continue9
10 health.current -= event.amount11 }12 }13}Other systems can react afterward.
For example:
1DamageSystem2 ↓3DeathSystem4 ↓5LootSystem6 ↓7RespawnSystemThis makes each system responsible for one clear part of the gameplay pipeline.
ECS Doesn't Mean Everything Must Be a System
One common mistake when learning ECS is trying to force absolutely everything into entities, components, and systems.
Your game will still have normal supporting architecture.
For example:
1Renderer2Asset Manager3Event Bus4Audio Manager5Input Manager6Networking7Save System8Scene LoaderSome of these may integrate closely with your ECS, but they don't necessarily need to become ECS components themselves.
ECS is an architectural tool, not a rule saying every line of your game must exist inside the ECS.
Is ECS Faster?
ECS is often discussed in terms of performance.
Large data-oriented ECS implementations can be extremely efficient because similar component data can be stored together in memory and processed in batches.
However, performance is not the only reason to use ECS.
For many JavaScript and indie game projects, the architectural benefits can be just as valuable:
- clear separation of responsibilities
- reusable gameplay logic
- easier composition
- fewer complicated inheritance trees
- easier addition of new gameplay features
- systems that can operate across many entity types
A simple ECS doesn't need to be an extremely optimized data-oriented engine to be useful.
When ECS Starts to Make Sense
ECS becomes especially useful when you start building systems that interact with each other.
For example:
1Input2 ↓3Player Controller4 ↓5Movement6 ↓7Collision8 ↓9Combat10 ↓11Damage12 ↓13Death14 ↓15LootYour player, enemies, NPCs, projectiles, and other game objects can participate in those systems based on which components they have.
As features increase, composition makes it easier to extend the game without creating enormous classes.
A Useful Mental Model
If you're struggling to understand whether something should be an Entity, Component, or System, try asking these three questions.
Entity
What thing am I talking about?
Examples:
1Player2Enemy3Sword4Chest5ProjectileComponent
What data or capability does that thing have?
Examples:
1Transform2Health3Inventory4Velocity5ColliderSystem
What logic operates on entities with that data?
Examples:
1MovementSystem2DamageSystem3InventorySystem4CollisionSystemA simple way to remember it is:
Entities are IDs. Components are data. Systems are logic.
That sentence captures the foundation of ECS surprisingly well.
Final Thoughts
Entity Component Systems can feel unusual if you're coming from traditional object-oriented programming.
Instead of creating increasingly specialised classes, ECS encourages you to build game objects through composition.
An entity is an identity.
Components describe the entity's data and capabilities.
Systems contain the logic that operates on those components.
Once that separation becomes familiar, ECS makes it much easier to reason about increasingly complex gameplay systems.
You don't need to build the world's most sophisticated ECS implementation to benefit from the architecture either.
Start simple.
Create entities.
Attach data-only components.
Build small systems with clear responsibilities.
Then let the architecture grow alongside your game.