Introduction
With the previous articles covering the foundations of Signals and fine-grained reactivity, you should now have a basic idea of what Signals are and why they exist.
In this article, we return to the core topic: how Signals actually work, including the roles of Effect, Computed, and the Scheduler.
The Core Pipeline: Data → Derivation → Side Effects
UI = f(state) becomes literally true in a fine-grained model: state is decomposed into many tiny, indivisible Signals.
- Anything derived from Signals becomes a Computed.
- Anything that touches the outside world—DOM, network, console, etc.—lives inside an Effect.
Signal — a Single Source of Truth
What a Signal Really Is
Think of a Signal as a small box that holds a value. This box has several important properties:
- Read the value: You can always get what’s inside the box.
- Write the value: If it’s a writable Signal, you can update the content.
- Notify dependents automatically: When the value changes, every computation or UI element that depends on it gets notified efficiently and will update accordingly.
Based on previous articles, a minimal Signal needs to support:
- Storing a mutable value
- Keeping a list of observers
- Tracking who is reading it
- Marking all observers as dirty when updated
Computed — Cached, Lazy, Pure Derived Values
Core ideas
- Lazy + cached: Recalculate only on first access or when its dependencies are marked dirty
- Is both an observer and observable
- Must be pure: No side effects; those belong inside Effects
A Computed tracks its dependencies automatically and only recomputes when needed, making it a perfect tool for cheap, predictable derivations.
Effect — The Executor of Side Effects & Scheduling Boundary
Core ideas
- Subscribes to relevant Signals or Computeds
- Actual execution timing is controlled by the Scheduler
- Performs only side effects (DOM updates, logs, fetches, etc.)
- Returns no value
Effects run when the system has stable, finalized data—never in the middle of a chain of updates.
Scheduler — The Gatekeeper of Batching & Priority
Why we need a Scheduler
The Scheduler orchestrates the entire pipeline:
- Collect all dirty Computeds and Effects
- Batch updates in the next microtask or RAF
- Recompute Computeds first
- Run Effects afterward (side effects last)
Typical behavior
- The setter only marks dependencies dirty
- The Scheduler runs asynchronously
- A microtask queue (e.g.,
queueMicrotask) orrequestAnimationFrameis commonly used to batch operations
This prevents unnecessary recomputation and ensures consistent update order.
Common Pitfalls and Solutions
| Scenario | Problem | Solution |
|---|---|---|
| Repeated writes | Effect performs multiple set() calls → excessive recomputation |
Use batch() or merge writes |
| Infinite loop | Effect writes to the same Signal it depends on | Detect in dev mode and throw |
| Dependency cycle | Computed A depends on B, B depends on A | Detect cycles during graph construction |
| Memory leaks | Effects created dynamically but never disposed | Auto-cleanup on unmount (cleanup functions) |
Summary
Here’s a compact recap of the runtime:
- Signal — the smallest mutable and trackable state unit
- Computed — lazy, cached, pure derived value
- Effect — the boundary where side effects live
- Scheduler — batches all updates and ensures correct ordering
Together, they form the foundation of fine-grained reactivity, enabling “write once, update only what changes” behavior.
Next Up
In the next article, we’ll explore how Dependency Tracking connects Signals, Computeds, and Effects into a consistent and efficient graph.






Top comments (0)