
ECS System Order Explained: Why Update Order Matters
One of the easiest ECS problems to overlook is system order.
You can build perfectly reasonable components and perfectly reasonable systems, yet still end up with broken movement, one-frame delays, incorrect collisions, jittery cameras, or UI that always seems slightly behind.
The reason is simple:
Systems may be separated by responsibility, but they still share and modify the same world state.
If one system expects another system to update some data first, the order in which those systems run becomes part of your game logic.
In this guide, we'll look at why system order matters, how to reason about dependencies between systems, and how to build a predictable ECS update pipeline for things like input, movement, physics, combat, cameras, UI, and rendering.
The Basic Problem
Imagine we have three systems:
PlayerControllerSystemGravitySystemMovementSystem
They all work with an entity's velocity or position.
The controller changes horizontal velocity.
Gravity changes vertical velocity.
Movement applies velocity to position.
A sensible order is:
1PlayerControllerSystem2 ↓3GravitySystem4 ↓5MovementSystemThat gives us:
- player input changes velocity
- gravity changes velocity
- movement uses the final velocity
Now imagine this instead:
1MovementSystem2 ↓3PlayerControllerSystem4 ↓5GravitySystemThe systems themselves have not changed.
But movement now happens before the controller and gravity update velocity.
The result is that position uses last frame's velocity.
That can create a subtle one-frame delay.
System Order Is Really Data Order
A useful way to think about system order is not:
Which system is most important?
Instead ask:
Which data must exist before another system can use it?
For example:
1InputSystemproduces input state.
Then:
1PlayerControllerSystemreads that input and modifies velocity.
Then:
1MovementSystemreads velocity and modifies position.
So the dependency is really:
1Input data2 ↓3Velocity4 ↓5PositionThe systems simply represent the stages that transform that data.
This is why system order can often be understood as a data pipeline.
A Simple Player Movement Pipeline
Let's imagine a player entity has:
Input, Velocity, Transform, Grounded, and ControllerSettings.
A basic movement frame might look like this:
1InputSystem2 ↓3PlayerControllerSystem4 ↓5GravitySystem6 ↓7MovementSystem8 ↓9CollisionSystemEach stage has a clear responsibility.
InputSystem
Captures raw input:
- W
- A
- S
- D
- jump
- sprint
- attack
- interact
It should usually run early because many other systems depend on input state.
PlayerControllerSystem
Interprets that input as gameplay intent.
For example:
1velocity.x = moveDirection.x * moveSpeed2velocity.z = moveDirection.z * moveSpeedIt may also decide whether the player is trying to jump.
GravitySystem
Updates vertical velocity:
1velocity.y += gravity * deltaTimeMovementSystem
Applies velocity:
1transform.position.x += velocity.x * deltaTime2transform.position.y += velocity.y * deltaTime3transform.position.z += velocity.z * deltaTimeCollisionSystem
Checks whether the resulting position intersects the world and corrects it if necessary.
The important thing is that each system receives data in the state it expects.
What Happens If Collision Runs Too Early?
Suppose collision runs before movement:
1CollisionSystem2 ↓3MovementSystemCollision checks the entity's current position.
Everything looks valid.
Then MovementSystem moves the entity directly into a wall.
No system runs afterward to resolve that penetration.
The entity may remain inside the wall until the next frame.
A better order is:
1MovementSystem2 ↓3CollisionSystemMovement proposes the new position.
Collision validates and corrects it.
This gives us a useful general pattern:
1Generate movement2 ↓3Apply movement4 ↓5Resolve movementSimulation Before Presentation
One of the most useful high-level rules is:
Update gameplay state before updating presentation.
Presentation systems include things such as:
- cameras
- health bars
- damage numbers
- target indicators
- UI panels
- rendering
Those systems should normally observe the final state for the frame.
For example, consider:
1MovementSystem2CameraSystemThe player moves first.
Then the camera follows the player's new position.
That makes sense.
If we reverse them:
1CameraSystem2MovementSystemthe camera follows the player's previous position.
The player then moves afterward.
That can create visible lag or jitter because the camera is always one simulation step behind.
Camera Order Matters More Than It Seems
Camera systems often depend on several earlier stages.
A third-person camera may need:
- the player's corrected position
- the player's rotation
- the current control mode
- the selected target
- camera collision information
A reasonable pipeline might be:
1Input2↓3Player Controller4↓5Movement6↓7Player Collision8↓9Camera10↓11Camera Collision12↓13RenderFirst, the player reaches a valid position.
Then the camera calculates where it wants to be.
Then camera collision makes sure the camera does not clip through walls.
Finally, the renderer draws the scene.
If CameraCollisionSystem ran before CameraSystem, it would be trying to correct a camera position that has not yet been calculated for the current frame.
Rendering Should Usually Be Last
A render system should normally be near the end of the frame.
Why?
Because rendering is the final presentation of the game state.
Ideally, the renderer sees:
- the final player position
- resolved collisions
- updated camera
- updated lights
- current UI state
- current target
- latest combat state
A simplified frame could look like:
1Input2↓3Gameplay4↓5Physics6↓7Camera8↓9UI10↓11RenderIf rendering happens halfway through the pipeline, anything updated afterward will not become visible until the next frame.
That can introduce visual inconsistencies.
A More Complete ECS Pipeline
As a game grows, the system list becomes much larger.
A practical pipeline might look something like:
11. Input22. Gameplay requests33. Character control44. Physics and movement55. Collision66. Camera77. Combat and stats88. Gameplay UI99. RenderWe can expand that further.
1InputSystem2MouseLookSystem3
4↓ Gameplay Requests5
6InteractionSystem7ItemUseRequestSystem8AbilityRequestSystem9
10↓ Character Control11
12ModeSwitchSystem13PlayerControllerSystem14GravitySystem15MovementSystem16
17↓ Collision18
19CapsuleCollisionSystem20AABBCollisionSystem21
22↓ Camera23
24CameraSystem25CameraCollisionSystem26LightSystem27
28↓ Combat / Stats29
30DamageSystem31StatsSystem32DeathSystem33RespawnSystem34
35↓ UI36
37StatsUISystem38TargetHealthBarUISystem39FloatingHealthBarSystem40DamageNumberUISystem41QuickbarUISystem42
43↓ Final Presentation44
45RenderSystemThis is not the one universally correct ECS order.
Different games will need different pipelines.
What matters is that the order reflects the dependencies between systems.
Input Should Usually Be Early
Input is often the starting point for an entire frame.
Imagine the player presses F to attack.
An InputSystem might update:
1input.attackPressed = trueThen an attack-related system can read it.
If attack processing runs before input:
1AttackSystem2↓3InputSystemthen AttackSystem won't see the button press until the next update.
Instead:
1InputSystem2↓3AttackSystemlets gameplay respond during the same frame.
This principle applies to:
- movement
- jumping
- interaction
- inventory toggles
- abilities
- targeting
- camera controls
Input and Gameplay Requests Can Be Separate
A useful architecture is to separate raw input from gameplay intent.
For example:
1InputSystem2 ↓3ItemUseRequestSystem4 ↓5ItemUseSystemInputSystem knows that the player pressed slot 1.
ItemUseRequestSystem translates that into:
The player wants to use the item in quickbar slot 1.
ItemUseSystem handles the actual gameplay rules.
This makes the order clear:
1Capture input2 ↓3Create intent4 ↓5Resolve gameplayIt also means the gameplay logic does not have to depend directly on keyboard input.
Later, controller input, AI, scripts, or network messages could potentially generate the same gameplay request.
Events Can Also Have Ordering Requirements
Using an EventBus reduces direct system coupling, but it does not eliminate ordering concerns.
Suppose DamageSystem emits:
1EVT_DAMAGE_RESOLVEDand DamageNumberUISystem consumes that event.
Then we need:
1DamageSystem2 ↓3DamageNumberUISystemif both operate during the same frame.
If the UI system runs first, the event does not exist yet.
Depending on the EventBus architecture, the event may:
- be consumed next frame
- remain queued
- be missed entirely
This is why event timing should be designed together with system timing.
Same-Frame Events vs Next-Frame Events
There are two useful event patterns.
Same-frame event
A system emits an event and a later system consumes it during the same frame.
1DamageSystem2 ↓ emit3EVT_DAMAGE_RESOLVED4 ↓ consume5DamageNumberUISystemThis is useful when immediate reaction is expected.
Next-frame event
A system intentionally queues something for the next update.
1System A2 ↓3emitNext(...)4 ↓5Next frame6 ↓7System BThis can help avoid situations where modifying state halfway through a pipeline would cause inconsistent behaviour.
Neither pattern is automatically better.
The important thing is that the behaviour is intentional.
Death and Damage Ordering
Combat is another area where order matters.
Imagine an entity has:
1Health.current = 5and receives:
110 damageA sensible flow is:
1Attack2↓3DamageSystem4↓5Health becomes 06↓7DeathSystem8↓9Dead state addedIf DeathSystem runs before DamageSystem, it checks the entity while health is still 5.
The entity survives until the next frame.
That may not be a serious bug, but it introduces unnecessary delay and can complicate other systems.
A cleaner order is:
1Damage resolution2 ↓3Death detection4 ↓5Death behaviourRespawn Must Happen After Death
The same idea applies to respawning.
You usually do not want:
1RespawnSystem2↓3DeathSystembecause the respawn system may evaluate an entity before the death system has marked it as dead.
Instead:
1DamageSystem2↓3DeathSystem4↓5RespawnSystemcreates a clear lifecycle.
Depending on the game's design, respawn may occur many frames later, but the dependency remains:
Death must be established before something can respawn.
Stats Should Be Ready Before Systems Use Them
Suppose you have:
BaseStats, FinalStats, and Equipment.
A StatsSystem calculates:
1Base attack2+3Equipment modifiers4=5Final attackNow suppose an attack system uses FinalStats.attack.
Which should run first?
If equipment can change during the frame:
1EquipmentSystem2↓3StatsSystem4↓5AttackSystemis safer than:
1AttackSystem2↓3StatsSystemOtherwise, the attack could use stale stats for one frame.
This becomes particularly important when gameplay state can change dynamically through:
- equipment
- buffs
- debuffs
- status effects
- temporary abilities
- level changes
UI Usually Reads State Rather Than Producing It
Most UI systems should observe gameplay state rather than control it directly.
For example:
1StatsSystem2↓3StatsUISystemThe stats system calculates the real values.
Then the UI displays them.
Likewise:
1DamageSystem2↓3Health updated4↓5HealthBarUISystemThe UI should not be responsible for deciding what the player's health actually is.
That separation makes ordering easier to reason about:
1Gameplay first2Presentation secondThere Are Exceptions
System order rules are not absolute.
For example, some UI systems produce gameplay requests.
An inventory UI might detect that the player clicked Equip.
That UI interaction could produce:
1EVT_EQUIP_ITEM_REQUESTThen gameplay systems resolve the request.
So the pipeline may sometimes look like:
1UI input2↓3Gameplay request4↓5Gameplay resolution6↓7UI refreshThe important distinction is that the UI can request something without becoming the authority that applies the gameplay rule.
Avoid Hidden Dependencies
System order becomes dangerous when dependencies are not obvious.
Suppose SystemB only works correctly if SystemA happened first, but nothing in the code makes that relationship clear.
Six months later, someone rearranges systems and suddenly introduces a bug.
This is why it helps to document the pipeline clearly.
For example:
1world.addSystem(new InputSystem(world))2world.addSystem(new PlayerControllerSystem(world))3world.addSystem(new GravitySystem(world))4world.addSystem(new MovementSystem(world))5world.addSystem(new CollisionSystem(world))6world.addSystem(new CameraSystem(world))7world.addSystem(new RenderSystem(world))The registration itself documents the execution order.
A short comment around major groups can make it even clearer.
Group Systems by Phase
As the engine grows, it helps to think in phases rather than one giant list.
For example:
1INPUT PHASE2
3InputSystem4MouseLookSystem5
6GAMEPLAY PHASE7
8InteractionSystem9ItemUseRequestSystem10ItemUseSystem11
12MOVEMENT PHASE13
14PlayerControllerSystem15GravitySystem16MovementSystem17
18PHYSICS PHASE19
20CapsuleCollisionSystem21AABBCollisionSystem22
23CAMERA PHASE24
25CameraSystem26CameraCollisionSystem27
28COMBAT PHASE29
30DamageSystem31StatsSystem32DeathSystem33
34UI PHASE35
36StatsUISystem37TargetHealthBarUISystem38DamageNumberUISystem39
40RENDER PHASE41
42RenderSystemThis makes the architecture much easier to understand.
You can immediately see both:
- what each system does
- roughly when it should run
Don't Create Too Many Phases Too Early
Phases are useful, but you don't need an enormous scheduler for a small project.
A simple array is often enough:
1this.systems = []and:
1update(deltaTime) {2 for (const system of this.systems) {3 system.update(deltaTime)4 }5}Then registration order defines execution order.
That approach is extremely easy to debug.
Only introduce more complicated scheduling when the engine actually needs it.
Fixed Timestep Adds Another Layer
Once physics or deterministic simulation becomes important, you may introduce a fixed timestep.
Instead of updating simulation at whatever FPS the renderer happens to achieve, gameplay might update at a fixed interval.
Conceptually:
1Render Frame2 │3 ├── Fixed Update4 ├── Fixed Update5 └── RenderSome frames may perform one simulation update.
Others may perform multiple updates.
Now system order exists at two levels:
- the order of systems inside the fixed simulation
- the relationship between simulation and rendering
A common structure is:
1Fixed Simulation2
3Input state4Controller5Gravity6Movement7Collision8Combat9
10↓11
12Presentation13
14Camera15UI16RenderExactly how this is divided depends on the game, but the principle remains the same:
Systems consuming simulation data should see a consistent state.
Multiplayer Makes Order Even More Important
System order also becomes important when preparing an ECS for multiplayer.
Networked games may need stages such as:
1Receive network input2↓3Validate input4↓5Simulation6↓7Resolve gameplay8↓9Create network snapshot10↓11Send stateIf snapshots are created before gameplay finishes updating, clients may receive incomplete state.
Deterministic simulation also requires systems to run in a predictable sequence.
That makes an explicit update pipeline valuable even before multiplayer exists.
Common System Order Bugs
Several bugs frequently come from incorrect ordering.
One-frame input delay
Cause:
PlayerControllerSystem runs before InputSystem.
Camera feels like it is lagging behind
Cause:
CameraSystem runs before movement or collision.
Player temporarily clips into walls
Cause:
collision runs before movement instead of after it.
UI shows old values
Cause:
UI renders before gameplay systems calculate the new state.
Death occurs one frame late
Cause:
DeathSystem checks health before DamageSystem modifies it.
Equipment bonuses apply one frame late
Cause:
combat uses FinalStats before StatsSystem recalculates them.
Visual state does not match simulation
Cause:
RenderSystem runs before the simulation is finished.
When a bug feels like the game is one frame behind, system order should be one of the first things you inspect.
How to Decide Where a New System Goes
When adding a system, ask three questions.
What data does this system read?
For example, MovementSystem reads Velocity.
What data does this system write?
It writes Transform.position.
Which systems produce or consume that data?
PlayerControllerSystem may produce velocity.
CollisionSystem may consume the resulting transform.
That tells us:
1PlayerControllerSystem2↓3MovementSystem4↓5CollisionSystemThis simple read/write analysis makes many ordering decisions obvious.
A Useful Dependency Example
Suppose we add:
InputSystem, PlayerControllerSystem, GravitySystem, MovementSystem, CollisionSystem, CameraSystem, and RenderSystem.
We can describe their dependencies like this:
1InputSystem2writes Input3
4PlayerControllerSystem5reads Input6writes Velocity7
8GravitySystem9writes Velocity10
11MovementSystem12reads Velocity13writes Transform14
15CollisionSystem16reads/writes Transform17
18CameraSystem19reads final Transform20
21RenderSystem22reads final world/camera stateThat naturally produces:
1Input2↓3Controller4↓5Gravity6↓7Movement8↓9Collision10↓11Camera12↓13RenderThe pipeline is not arbitrary.
It follows the flow of data.
A Practical ECS Pipeline
For a larger third-person RPG-style ECS, a practical structure could be:
1INPUT2InputSystem3MouseLookSystem4
5↓6
7REQUESTS / INTERACTION8InteractionSystem9ItemUseRequestSystem10AbilityRequestSystem11
12↓13
14CHARACTER CONTROL15ModeSwitchSystem16PlayerControllerSystem17GravitySystem18MovementSystem19
20↓21
22PHYSICS23CapsuleVsAABBCollisionSystem24AABBCollisionSystem25
26↓27
28CAMERA29CameraSystem30CameraCollisionSystem31LightSystem32
33↓34
35COMBAT / GAMEPLAY36DamageSystem37StatsSystem38DeathSystem39RespawnSystem40
41↓42
43TARGETING / FEEDBACK44TargetingSystem45HitFlashSystem46DamageNumberUISystem47
48↓49
50UI51InventoryUISystem52StatsUISystem53QuickbarUISystem54TargetHealthBarUISystem55FloatingHealthBarSystem56
57↓58
59RENDER60RenderSystemThe exact order within some groups can change depending on implementation.
For example, targeting may need to run earlier if combat directly depends on the selected target during the same frame.
What matters is that those dependencies are intentional.
Keep the Pipeline Easy to Read
A system pipeline should not require archaeology to understand.
If you open the engine bootstrap code, it should be reasonably obvious what happens first and what happens last.
That makes debugging much easier.
When something goes wrong, you can ask:
Is this system receiving the state from before or after another system runs?
Often, simply printing the system sequence reveals the issue.
Avoid Solving Every Dependency With Direct Calls
One temptation is to avoid ordering issues by calling systems directly.
For example:
1movementSystem.update()2collisionSystem.update()3cameraSystem.update()inside another system.
That creates tight coupling.
Now one system is effectively managing the others.
A cleaner approach is usually to let the world own the pipeline:
1world.addSystem(new MovementSystem(world))2world.addSystem(new CollisionSystem(world))3world.addSystem(new CameraSystem(world))Each system remains independent.
The world determines execution order.
A Simple Rule of Thumb
When you're unsure how to order systems, think:
1CAPTURE2↓3INTERPRET4↓5SIMULATE6↓7RESOLVE8↓9PRESENTFor example:
1Capture2InputSystem3
4Interpret5PlayerControllerSystem6
7Simulate8GravitySystem9MovementSystem10
11Resolve12CollisionSystem13DamageSystem14DeathSystem15
16Present17CameraSystem18UISystems19RenderSystemNot every game fits this perfectly, but it is a useful starting point.
System Order Is Part of Your Architecture
It is easy to think of ECS architecture as only:
- entities
- components
- systems
- queries
- events
But execution order is also part of the architecture.
Two projects can contain the exact same systems and components yet behave differently because their pipelines are different.
That makes system registration something worth designing deliberately rather than treating as boilerplate.
Conclusion
Separating gameplay into systems is one of the strengths of ECS, but those systems do not operate in isolation.
They form a pipeline.
One system may create the data that another system consumes later in the same frame.
That means update order directly affects gameplay behaviour.
A useful way to reason about the pipeline is:
1What does this system read?2What does this system write?3Who needs that data next?From there, many ordering decisions become straightforward.
For a typical game, the high-level flow often becomes something like:
1Input2↓3Gameplay Requests4↓5Character Control6↓7Movement8↓9Collision10↓11Camera12↓13Combat / Stats14↓15UI16↓17RenderThe exact systems will change as the game grows, but the principle remains the same:
Produce state before another system consumes it, resolve gameplay before presentation, and make dependencies explicit.
A well-designed ECS pipeline makes behaviour easier to predict, bugs easier to diagnose, and future systems easier to integrate.
And as the engine grows into physics, abilities, status effects, AI, multiplayer, and more advanced combat, having a clear update order becomes increasingly valuable.

Build a simple Entity Component System in JavaScript from scratch with entities, data-only components, queries, systems, and a working update loop.

Learn how to decide whether game logic belongs in an Entity, Component, or System when building an Entity Component System in JavaScript.

Learn how to decide whether game logic belongs in an Entity, Component, or System when building an Entity Component System in JavaScript.