DEV Community

Cover image for Understanding How Signals Work
Luciano0322
Luciano0322

Posted on

Understanding How Signals Work

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

signal flow

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.

signal box

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

computed timeline

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

effect timeline

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:

  1. Collect all dirty Computeds and Effects
  2. Batch updates in the next microtask or RAF
  3. Recompute Computeds first
  4. Run Effects afterward (side effects last)

scheduler timeline

Typical behavior

  • The setter only marks dependencies dirty
  • The Scheduler runs asynchronously
  • A microtask queue (e.g., queueMicrotask) or requestAnimationFrame is commonly used to batch operations

scheduler flow

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)