A WebSocket emits events for different entities interleaved: update id=a, update id=b, update id=a. Each entity should update ITS OWN state independently. Logically N streams, physically one.
The problem it solves
One shared reducer with nested maps (state[id].counter += delta) quickly gets complex. Mutations kill the entities' independence. Testing "this event sequence leads to state X for id=a" is awkward.
Operators and why they matter
groupBy(event => event.id) — splits one stream into N streams by the id key.
mergeMap(group$ => ...) — subscribe to each group.
scan inside the group — an independent reducer for that specific entity.
map — build the per-entity update.
Gotchas
groupBy with unbounded ids — a memory leak; groups are kept forever. Use a durationSelector for cleanup.
scan mutating a shared object instead of immutable updates — breaks the groups' independence.
If the global order of changes across entities matters, mergeMap is too loose. You'll need concatMap or extra synchronization.
What you get
Each entity gets its own independent state stream. The logic is clean, testable, and free of nested mutations.