DEV Community

Cover image for Push-based vs. Pull-based Reactivity: The Two Driving Models Behind Fine-Grained Systems
Luciano0322
Luciano0322

Posted on

Push-based vs. Pull-based Reactivity: The Two Driving Models Behind Fine-Grained Systems

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

push flow

Pull-based

pull flow


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)