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

  1. Remove the synchronous write from the effect — move it to an event handler so the state change is user-driven, not reactive.
  2. Guard the write with a condition so it only runs when the value actually changes (e.g. `if (x !== next) x = next`).
  3. Split the logic: use $derived for the computed value instead of $effect + state write.
  4. 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

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


AI-assisted analysis of sveltejs/svelte@20b341f100 (2026-08-12). Data as JSON: /api/errors/1d91811b594ea433. Report an issue: GitHub.