Skip to content

Interactive Demos

See the Diamond Dependency Glitch in Action

Section titled “See the Diamond Dependency Glitch in Action”

When two derived values depend on the same source (a diamond dependency), some reactive libraries emit inconsistent intermediate states — known as glitches. This demo lets you see the difference with your own eyes.

Click and drag on each canvas to draw a trail. Each dot is placed by the library’s reactive pipeline using the same diamond dependency graph:

mousePos (source: {x, y})
├── derivedX = map(pos => pos.x)
├── derivedY = map(pos => pos.y)
└── combine([derivedX, derivedY])
└── draw dot at {x, y} + check consistency

Each emitted position is compared to the latest mouse position:

  • Blue dot — emitted (x, y) matches the actual mouse position (consistent).
  • Red dot — one axis updated but the other didn’t yet (glitch — the dot appears off the drag path).
LibraryGlitchesWhy
SynState0derivedX and derivedY are both recomputed, in depth order, before combine and before any subscriber
RxJSManycombineLatest emits as soon as derivedX changes, while derivedY still holds the previous position, then again
Jotai0The subscribed atom is recomputed when it is read, after both inputs
MobX0The reaction reads the computed values after the action that moved the mouse, when every input is up to date
Preact Signals0The effect reads computed values, and each is recomputed on read once one of its sources has a newer version
Solid0Every memo below the write is marked first, and the memos rerun before the effect, which then sees both new values

Five of the six are glitch-free on this diamond, since in each of the five the guarantee covers derived values. Where they part is a step written as an effect or reaction that writes a value, such as one that starts a request; Operators on Signals shows that case. RxJS’s extra emissions also grow with the graph, which the next demo counts.

In real applications, diamond dependencies appear everywhere — computed styles from shared state, derived UI values, form validations. Glitches cause:

  • Visual flicker — UI renders an impossible intermediate state.
  • Wasted work — each extra emission triggers unnecessary side effects. With NN inputs to combineLatest, a single source update causes N+1N+1 emissions — this scales to an O(N2)O(N^2) performance cliff as shown in the demo below.
  • Logic bugs — side effects fire with inconsistent data.

A glitch-free library, SynState among them, rules these out at the library level, with no manual workarounds needed.


Deep Dependency Chain: Propagation Under Load

Section titled “Deep Dependency Chain: Propagation Under Load”

This demo builds the same reactive graph in each of six libraries: a depth-NN chain of stateful nodes where each depends on the previous, with a final combine/derive that reads all N+1N+1 outputs.

A snake tail follows the mouse — each segment is a stage in the chain that smoothly follows the previous stage via linear interpolation (lerp). To draw the full snake, all N+1N+1 outputs (head + NN stages) are collected into a single combine:

The diagram shows N=3N = 3. In the demo, NN is adjustable via a slider. Increase the chain depth and compare the Total updates counters.

  • Total updates counts the calls to each library’s subscriber. Five of the six count one per mouse move, whatever NN is.
  • RxJS counts roughly N+1N+1 per mouse move, so its counter pulls away as NN grows, and at high NN its canvas stutters.
  • Avg μs/update is how long each subscriber call takes to draw the snake. It leaves out the propagation, so it is about the same for every library; the difference is in how many calls there are. The benchmark’s deep chain scenario measures propagation through a chain of this shape.

Check the Total updates counter: RxJS shows roughly N+1N+1 times more updates than SynState for the same mouse movement. combineLatest fires every time any of its N+1N+1 inputs emits. On a single mouse move, the scan chain propagates sequentially:

  1. source emits → combineLatest fires (only source updated, scans still hold old values)
  2. scan₁ emits → combineLatest fires again
  3. scan₂ emits → fires again
  4. … for all NN stages

Each of these N+1N+1 firings triggers a full canvas redraw of N+1N+1 points → O(N2)O(N^2). This is the glitch problem from the first demo manifesting as a performance cliff.

Each library writes a stateful stage in its own idiom, shown in the second column; Evaluation Models describes how each one propagates in general.

LibraryStages in this demoSubscriber calls per mouse eventWork per event
SynStatescan operators, pushed in depth order from the root1 (combine)O(N)O(N)
RxJSscan operators, pushed depth-first per subscriptionN+1N+1 (combineLatest)O(N2)O(N^2)
JotaiselectAtom atoms, each derived from the one before1 (store.sub)O(N)O(N)
MobXBoxes that one reaction writes in a single action1 (autorun)O(N)O(N)
Preact SignalsSignals that one effect writes in a single batch1 (effect)O(N)O(N)
SolidMemos given their previous value, rerun after marking1 (createEffect)O(N)O(N)

Every library but RxJS calls its subscriber once per mouse event, so each does work proportional to NN. What one update costs within that is a constant that depends on the library and on the shape of the graph; the next demo makes it visible, and the benchmark measures it.


Throughput: Per-Update Overhead Under Load

Section titled “Throughput: Per-Update Overhead Under Load”

This demo leaves out RxJS, whose O(N2)O(N^2) update count would dominate, and runs the five libraries that call their subscriber once per update.

A ball automatically orbits in a circle. Each canvas runs the same depth-MM reactive chain as the spring demo above. The difference: instead of 1 source update per mouse event, KK source updates are pushed through the chain every animation frame in a tight synchronous loop.

each animation frame:
for k in 0..K-1:
update source position (micro-step along orbit)
→ propagate through depth-M scan chain
→ subscriber records latest points
draw snake once

This amplifies the per-update overhead by a factor of KK. When the overhead exceeds 16ms, the animation drops frames and stutters.

  • ms/frame is how long the KK updates of one frame take, measured in this browser on this device. It turns red past 16ms, the budget of a 60fps frame.
  • Raise KK and MM until a canvas stutters. Which one does first, and at what KK, depends on the browser, the device and the rest of the page, so these numbers are not a benchmark: the benchmark’s deep chain scenario runs this same graph under controlled conditions.
  • What each library does per update, and when, is in Evaluation Models.