Recap
Building on the previous article about the core ideas behind reactivity, this part clarifies the difference between Push-based and Pull-based reactivity models.
Core Idea
In fine-grained reactivity:
- Push-based systems perform computation immediately when a value changes.
- Pull-based systems delay computation until the moment someone reads the value.
Let’s look at them through real-world analogies.
Real-World Examples
Push-based
Imagine ordering food in a food court.
- Write (ordering): You place your order.
- Push (notify on completion): When your meal is ready, your buzzer vibrates or lights up — the update is pushed all the way to you.
- Effect (pick up): You walk to the counter to pick it up.
- Reactivity interpretation: When a source changes, dependent nodes are recomputed immediately and notified right away.
Pull-based
Now imagine buying a bubble tea.
- Write (ordering): You place your drink order.
- Mark (state updated only): When the drink is ready, the shop just posts your number on a screen — they don’t notify you directly.
- Read → compute (only when needed): When you look up to check the screen, that read operation triggers “oh, it’s ready, I should go pick it up.”
- Effect (pick up): You go to the counter.
- Reactivity interpretation: Writes only mark nodes as dirty; real computation happens later when someone reads the value.
Formal Definitions
| Model | Behavior Focus | Simplified Flow |
|---|---|---|
| Push-based | Compute on write: updates propagate immediately | set() → propagate → compute → effect |
| Pull-based | Mark on write, compute on read | set() → markDirty ⏸ read() → if dirty → compute → effect |
Key insight:
Both models “push” signals —
but Push pushes the computation,
while Pull pushes the dirty mark.
Timeline Diagrams
Push-based
Pull-based
Pros & Cons
| Aspect | Push-based | Pull-based |
|---|---|---|
| Read latency | Lowest — always fresh | First read may trigger recomputation |
| Write cost | Potentially high: O(depth × writes)
|
Lower: mostly O(depth × 1) (mark dirty only) |
| Over-computation | High — computes even if never read | Low — compute only when someone actually reads |
| Batching | Hard — work is already done | Natural fit — flush later in one batch |
| Debug visibility | Dependency chain expands immediately | Need DevTools to inspect when pulling happens |
| Best use cases | High-frequency writes, low reads (e.g., cursor syncing in collaboration apps) | Low writes, high reads (dashboards, charts) |
Note: even Pull-based systems still must walk the dependency graph during marking, but they do not recompute — they only set dirty = true.
How to Choose?
It depends entirely on the scenario. Modern reactivity libraries often combine both strategies.
| Use Case | Recommended Model | Reason |
|---|---|---|
| Real-time collaboration, game state sync | Push | Immediate reflection is more important than avoiding extra recomputation |
| Large dashboards, data visualizations | Pull | Writes are rare, reads are frequent — compute only what is actually needed |
| Timeline / scroll-driven animations | Pull + Scheduler | Pull defers work; scheduler ensures recomputation happens at most once per frame |
| Data pipelines (expensive computations reused many times) | Push-on-Commit | Compute once upfront, then reuse everywhere |
React is basically Pull + Scheduler, which is why batching works the way it does.
RxJS and MobX are classic examples of Push-on-Commit.
Common Misconceptions
“Pull means scanning the whole graph!”
No — pull only checks the relevant dependency chain upward when a value is read.
No full-tree traversal required.
“Push always wastes computation.”
Not when the result is guaranteed to be consumed immediately (e.g., cursor movement).
Lower read latency > cost of extra writes.
“Push vs Pull is either/or.”
Most modern signal systems use a hybrid push-pull approach:
- Push during writes → propagate dirty flags
- Pull during reads → compute only when needed
This combines responsiveness and laziness.
“Why do even fine-grained systems still need Pull?”
Because we often don’t know:
- Will this value ever be read?
- When will it be read? Pull separates:
- “The data changed” (mark dirty) from
- “Do I need to act on this?” (compute on read)
Conclusion
Why does fine-grained reactivity need a Push vs. Pull discussion?
In coarse-grained systems (like React’s Virtual DOM), diffing the whole tree is abstract enough.
But in signal-based systems, one single set() may fan out to hundreds of tiny derivations.
Choosing when computation happens affects:
- Total compute cost (performance)
- Interaction latency (UI smoothness)
- Scheduling behavior (avoiding jitter & dropped frames)
Understanding Push vs. Pull gives you the mental model and vocabulary needed to evaluate different reactivity frameworks.
Next Up
In the next article, we’ll explore how different mainstream frameworks design their reactivity systems — and why they made those choices.


Top comments (0)