This document describes Svelte's fine-grained reactivity system, which powers reactive state, computed values, and side effects. The reactivity system implements a signals-based architecture with Source, Derived, and Effect primitives. It uses dependency tracking, status flags, and a batching mechanism to efficiently propagate changes through the reactive graph.
For information about the compiler's role in transforming runes to these primitives, see Runes System. For details on the Batch system and async handling, see Batch System and Async Handling. For component lifecycle integration, see Component Lifecycle.
The reactivity system is built on three core signal types, each representing a different role in the reactive graph:
Sources: packages/svelte/src/internal/client/reactivity/types.d.ts11-89
A Source represents mutable reactive state, created by the $state rune or internally via source() and mutable_source():
Key functions:
source(value) - creates an immutable source packages/svelte/src/internal/client/reactivity/sources.js69-88mutable_source(value, immutable, trackable) - creates a mutable source packages/svelte/src/internal/client/reactivity/sources.js111-124set(source, value) - updates a source and marks reactions packages/svelte/src/internal/client/reactivity/sources.js146-259Sources: packages/svelte/src/internal/client/reactivity/sources.js69-124 packages/svelte/src/internal/client/reactivity/types.d.ts34-59
A Derived is a computed signal that lazily evaluates its function when read, caching the result until dependencies change:
Key functions:
derived(fn) - creates a derived signal packages/svelte/src/internal/client/reactivity/deriveds.js63-97execute_derived(derived) - evaluates the derived function packages/svelte/src/internal/client/reactivity/deriveds.js307-342update_derived(derived) - checks and updates if needed packages/svelte/src/internal/client/reactivity/deriveds.js348-386Sources: packages/svelte/src/internal/client/reactivity/deriveds.js63-97 packages/svelte/src/internal/client/reactivity/types.d.ts61-82
An Effect represents a side effect that re-runs when its dependencies change:
Key functions:
user_effect(fn) - creates a $effect packages/svelte/src/internal/client/reactivity/effects.js196-218render_effect(fn) - creates a render effect packages/svelte/src/internal/client/reactivity/effects.js355-357update_effect(effect) - executes the effect packages/svelte/src/internal/client/runtime.js429-483Sources: packages/svelte/src/internal/client/reactivity/effects.js86-172 packages/svelte/src/internal/client/reactivity/types.d.ts84-122
The reactivity system tracks dependencies using a push-pull model where reads (get()) establish dependencies and writes (set()) notify consumers.
Sources: packages/svelte/src/internal/client/runtime.js225-358 packages/svelte/src/internal/client/runtime.js522-679
The runtime maintains global state to track the currently executing reaction:
| Variable | Type | Purpose |
|---|---|---|
active_reaction | Reaction | null | Current reaction being updated (null for branch/root effects) |
active_effect | Effect | null | Current effect being executed |
current_sources | Source[] | null | Sources created within the current reaction |
new_deps | Value[] | null | Dependencies discovered during current update |
skipped_deps | number | Count of dependencies reused from previous run |
untracked_writes | Source[] | null | Writes to track for self-invalidation |
Sources: packages/svelte/src/internal/client/runtime.js72-127
When get(signal) is called during a reaction update:
Sources: packages/svelte/src/internal/client/runtime.js522-679
The system uses version numbers to optimize dependency tracking:
read_version - Incremented on each update_reaction() call, used to deduplicate dependencies within a single updatewrite_version - Incremented on each set() call, stored in signal.wv to determine if dependents are stalesignal.rv - The read version when this signal was last accessed in the current reactionsignal.wv - The write version when this signal was last modifiedreaction.wv - The write version when this reaction last ranThis allows quick staleness checks: if (dependency.wv > reaction.wv) means the dependency changed since the reaction ran.
Sources: packages/svelte/src/internal/client/runtime.js133-147 packages/svelte/src/internal/client/runtime.js155-193
Signals and effects use bitwise flags to efficiently encode their state and behavior:
| Flag | Value | Meaning |
|---|---|---|
CLEAN | 1 << 10 | Signal is up-to-date, no need to recompute |
MAYBE_DIRTY | 1 << 12 | Signal might be stale, check dependencies |
DIRTY | 1 << 11 | Signal is definitely stale, must recompute |
DESTROYED | 1 << 14 | Effect has been destroyed |
REACTION_RAN | 1 << 15 | Reaction has executed at least once |
| Flag | Value | Meaning |
|---|---|---|
DERIVED | 1 << 1 | Signal is a derived computation |
EFFECT | 1 << 2 | Signal is a user effect |
RENDER_EFFECT | 1 << 3 | Signal is a render effect |
BLOCK_EFFECT | 1 << 4 | Effect manages a block of DOM |
BRANCH_EFFECT | 1 << 5 | Effect represents a control flow branch |
ROOT_EFFECT | 1 << 6 | Effect is a component root |
CONNECTED | 1 << 9 | Derived is connected to the reactive graph |
INERT | 1 << 13 | Effect is paused (e.g., during outro) |
Sources: packages/svelte/src/internal/client/constants.js1-51
Sources: packages/svelte/src/internal/client/runtime.js155-193
When a source is updated, changes propagate through the reactive graph in two phases:
Sources: packages/svelte/src/internal/client/reactivity/sources.js174-259 packages/svelte/src/internal/client/reactivity/sources.js322-370
Sources: packages/svelte/src/internal/client/reactivity/batch.js184-234 packages/svelte/src/internal/client/runtime.js429-483
Before executing a derived or effect, is_dirty() determines if it needs to run:
Sources: packages/svelte/src/internal/client/runtime.js155-193
The Batch class coordinates updates, allowing multiple state changes to be processed together and supporting advanced features like time-traveling and forks.
Sources: packages/svelte/src/internal/client/reactivity/batch.js74-149
When multiple batches exist (e.g., during async work), the system uses batch_values to provide batch-specific views of state:
This allows reactions in Batch 1 to see the value "A" even though the underlying source.v is "B".
Sources: packages/svelte/src/internal/client/reactivity/batch.js513-531 packages/svelte/src/internal/client/runtime.js670-672
When a batch commits while other batches are pending, it must "rebase" those batches:
Sources: packages/svelte/src/internal/client/reactivity/batch.js351-427
To prevent memory leaks and unnecessary computation, deriveds that are not depended on by any effect can be disconnected from the reactive graph.
A derived is disconnected when:
reactions === null)CONNECTED flag is clearedSources: packages/svelte/src/internal/client/runtime.js366-409
When a disconnected derived is read during an effect, it reconnects:
Sources: packages/svelte/src/internal/client/runtime.js624-699
Effects created inside deriveds (e.g., from $effect in a derived function) are "frozen" when the derived disconnects:
When the derived reconnects, these effects can be unfrozen and re-executed.
Sources: packages/svelte/src/internal/client/reactivity/deriveds.js391-426
Effects form a tree structure where each effect can have child effects. This hierarchy is crucial for proper lifecycle management and DOM updates.
Sources: packages/svelte/src/internal/client/reactivity/effects.js69-172
Effects within the same parent are organized as a doubly-linked list:
| Property | Type | Purpose |
|---|---|---|
parent | Effect | null | Parent effect |
first | Effect | null | First child effect |
last | Effect | null | Last child effect |
prev | Effect | null | Previous sibling |
next | Effect | null | Next sibling |
Sources: packages/svelte/src/internal/client/reactivity/types.d.ts84-122
Sources: packages/svelte/src/internal/client/reactivity/effects.js86-172
Sources: packages/svelte/src/internal/client/reactivity/effects.js494-542
Effects can be paused during outro transitions and resumed during intros:
Sources: packages/svelte/src/internal/client/reactivity/effects.js588-694
The untrack() function prevents dependency tracking within a specific scope:
Sources: packages/svelte/src/internal/client/runtime.js745-753
Effects can invalidate themselves through untracked_writes:
This ensures effects like $effect(() => count++) can trigger themselves.
Sources: packages/svelte/src/internal/client/runtime.js295-308 packages/svelte/src/internal/client/runtime.js200-222
packages/svelte/src/internal/client/runtime.js
get(signal) - Read a signal and track dependency packages/svelte/src/internal/client/runtime.js522-679is_dirty(reaction) - Check if reaction needs to run packages/svelte/src/internal/client/runtime.js155-193update_reaction(reaction) - Execute and update dependencies packages/svelte/src/internal/client/runtime.js225-358update_effect(effect) - Execute effect function packages/svelte/src/internal/client/runtime.js429-483packages/svelte/src/internal/client/reactivity/sources.js
source(value) - Create immutable source packages/svelte/src/internal/client/reactivity/sources.js69-88mutable_source(value) - Create mutable source packages/svelte/src/internal/client/reactivity/sources.js111-124set(source, value) - Update source packages/svelte/src/internal/client/reactivity/sources.js146-166internal_set(source, value) - Internal update packages/svelte/src/internal/client/reactivity/sources.js174-259mark_reactions(signal, status) - Propagate changes packages/svelte/src/internal/client/reactivity/sources.js322-370packages/svelte/src/internal/client/reactivity/deriveds.js
derived(fn) - Create derived signal packages/svelte/src/internal/client/reactivity/deriveds.js63-97execute_derived(derived) - Run computation packages/svelte/src/internal/client/reactivity/deriveds.js307-342update_derived(derived) - Update if needed packages/svelte/src/internal/client/reactivity/deriveds.js348-386freeze_derived_effects(derived) - Pause owned effects packages/svelte/src/internal/client/reactivity/deriveds.js391-411unfreeze_derived_effects(derived) - Resume owned effects packages/svelte/src/internal/client/reactivity/deriveds.js416-426packages/svelte/src/internal/client/reactivity/effects.js
create_effect(type, fn, sync) - Core effect creator packages/svelte/src/internal/client/reactivity/effects.js86-172user_effect(fn) - Create $effect packages/svelte/src/internal/client/reactivity/effects.js196-218render_effect(fn) - Create render effect packages/svelte/src/internal/client/reactivity/effects.js355-357destroy_effect(effect) - Destroy effect packages/svelte/src/internal/client/reactivity/effects.js494-542pause_effect(effect) - Pause for transitions packages/svelte/src/internal/client/reactivity/effects.js588-608resume_effect(effect) - Resume after pause packages/svelte/src/internal/client/reactivity/effects.js652-654packages/svelte/src/internal/client/reactivity/batch.js
Batch class - Coordinates updates packages/svelte/src/internal/client/reactivity/batch.js74-531schedule_effect(effect) - Queue effect packages/svelte/src/internal/client/reactivity/batch.js579-668flushSync(fn) - Synchronous flush packages/svelte/src/internal/client/reactivity/batch.js540-573flush_effects() - Process queued effects packages/svelte/src/internal/client/reactivity/batch.js670-689Sources: All function references in this section link to their respective implementations in the codebase.
Refresh this wiki