Glitch-freedom is one of those things that does not bite you until it does, and then you spend a day debugging a UI that renders an impossible intermediate state for a single frame. I hit this in a dashboard project where two derived signals depended on the same source, and without batched updates the downstream computation ran with one stale and one fresh value. The result was a brief negative number in a percentage display, which was only visible if you knew to look for it.

The push-pull approach described here actually sidesteps the worst glitches because the dirty-flag propagation is just marking, not computing. But the article glosses over what happens during the pull phase when the dependency graph has diamonds. Topological sorting during pull is the standard fix -- Preact Signals and SolidJS both do this -- but it adds complexity that matters if you are rolling your own.

Flapjax was doing a lot of this right in 2008. It is wild that the JS ecosystem took another 15 years to converge on essentially the same core ideas with better ergonomics.

wouldn't this be solved by synchronously invalidating everything before computing anything? it seems like that's what the described system is doing tbh, since `setValue` does a depth-first traversal before returning. or is there a gap where that strategy fails you?

So yeah topological sorting is one element, but that global stack is a data race! You need to test set inclusion AND insert into it in an ordered way. Global mutex is gross. To do so lock-free could maybe be done with a lock free concurrent priority queue with a pair of monatomic generation counters for the priorities processed then next, then some memo of updates so that the conflicting re-update is invalidated by violation the generation constraint. I see no less than 3 CAS, so updates across a highly contentious system get fairly hairy. But still, a naive approach is good enough for the 99% so let there be glitches!

Can the reactive graph even be updated concurrently if the UI depends on it though? Because the UI is likely to run in its own single thread...

yea, this is in javascript. it's inherently single-threaded in almost all contexts (e.g. node.js shared memory where you're intentionally bypassing core semantics for performance, and correctness is entirely on you)

[deleted]