sveltejs/svelte · critical · Error
effect_update_depth_exceeded
effect_update_depth_exceeded
Error message
effect_update_depth_exceeded Maximum update depth exceeded. This typically indicates that an effect reads and writes the same piece of state https://svelte.dev/e/effect_update_depth_exceeded
What it means
Thrown by infinite_loop_guard() in reactivity/batch.js:1067 after the batch flush loop exceeds Svelte's maximum update iteration count. The guard exists because effects that synchronously read and then write the same state re-trigger themselves forever; rather than freeze the browser Svelte aborts and routes the error to the nearest boundary (batch.js:1066-1077).
Source
Thrown at packages/svelte/src/internal/client/errors.js:247
throw error;
} else {
throw new Error(`https://svelte.dev/e/effect_pending_outside_reaction`);
}
}
/**
* Maximum update depth exceeded. This typically indicates that an effect reads and writes the same piece of state
* @returns {never}
*/
export function effect_update_depth_exceeded() {
if (DEV) {
const error = new Error(`effect_update_depth_exceeded\nMaximum update depth exceeded. This typically indicates that an effect reads and writes the same piece of state\nhttps://svelte.dev/e/effect_update_depth_exceeded`);
error.name = 'Svelte error';
throw error;
} else {
throw new Error(`https://svelte.dev/e/effect_update_depth_exceeded`);
}
}
/**
* Cannot use `flushSync` inside an effect
* @returns {never}
*/
export function flush_sync_in_effect() {
if (DEV) {
const error = new Error(`flush_sync_in_effect\nCannot use \`flushSync\` inside an effect\nhttps://svelte.dev/e/flush_sync_in_effect`);
error.name = 'Svelte error';
throw error;
} else {
throw new Error(`https://svelte.dev/e/flush_sync_in_effect`);
}
}View on GitHub (pinned to 20b341f100)
Solutions
- Remove the synchronous write from the effect — move it to an event handler so the state change is user-driven, not reactive.
- Guard the write with a condition so it only runs when the value actually changes (e.g. `if (x !== next) x = next`).
- Split the logic: use $derived for the computed value instead of $effect + state write.
- Use untrack(() => {...}) around the write if you intentionally want to break the reactive dependency, though restructuring is preferred.
Example fix
// before
let count = $state(0);
$effect(() => { count = count + 1; }); // infinite loop
// after — derive instead of effect+write
let count = $state(0);
let doubled = $derived(count * 2);
// or move the increment to a user action
function increment() { count += 1; } Defensive patterns
Strategy: try-catch
Validate before calling
// Before shipping, audit each $effect: ensure it does not synchronously write
// to any $state/$derived it also reads.
// Quick static check pattern:
function assertEffectNotCyclic(stateWrites, stateReads) {
const overlap = stateWrites.filter(k => stateReads.includes(k));
if (overlap.length) throw new Error('Effect writes and reads: ' + overlap.join(', '));
} Try / catch
// Wrap state writes in effects with a change guard to prevent self-retrigger:
$effect(() => {
const next = compute(data);
if (next !== cached) { cached = next; stateVar = next; }
});
// For robustness against runaway loops, you may also catch in an error boundary:
// <svelte:boundary onerror={(e) => logCyclicError(e)}>...</svelte:boundary> Prevention
- Prefer $derived over $effect-plus-state-write for computed values.
- Inside $effect, only write state in response to external events (user input, timers), never unconditionally.
- Add an inequality guard before any write inside an effect.
- Use Svelte DevTools to see which effects re-run and why.
When it happens
Trigger: A $effect whose body both reads a $state/$derived and synchronously writes to it (or to a dependency that feeds back), causing the effect to reschedule on every flush. Also: mutual effects that re-trigger each other, or setting state unconditionally inside an effect callback.
Common situations: Writing `let count = $state(0); $effect(() => { count = count + 1; })`; updating a store a component subscribes to inside an effect that reads it; cascading effects where A writes B and B writes A; forgetting a condition guard so a 'last updated' timestamp always fires.
Related errors
- effect_in_unowned_derived
- effect_orphan
- effect_pending_outside_reaction
- flush_sync_in_effect
- get_abort_signal_outside_reaction
AI-assisted analysis of sveltejs/svelte@20b341f100 (2026-08-12).
Data as JSON: /api/errors/1d91811b594ea433.
Report an issue: GitHub.