Understanding Svelte 5 Runes: $derived vs $effect
Svelte 5 introduces a powerful new reactive primitive system called "runes" that simplifies state management. Let's explore what runes are and dive deep into two important ones: $derived and $effect. To demonstrate how to use these runes we will use a water tracker application that I've built. You can find the source code here: https://github.com/mikhail-karan/water-tracker-svelte5 What are Svelte 5 Runes? Runes are special primitives in Svelte 5 marked with a $ prefix that enable fine-grained reactivity. Unlike Svelte 4's compiler-based reactivity, runes bring reactivity directly into the JavaScript runtime, making it more explicit and portable. The main runes include: $state - Declares reactive state $derived - Computes values from state $effect - Runs side effects when dependencies change $props - Handles component props The Power of $derived $derived is used to compute values that depend on reactive state. It automatically updates whenever its dependencies change, and importantly, it's memoized (calculated only when needed). From our water tracker app: const blocksToFill = $derived(Math.min(Math.floor(waterConsumed / 100), 20)); const waterBlocks = $derived( Array(20) .fill(false) .map((_, index) => index 0 ? new Date() : null); Here we use $derived to: Calculate how many water blocks to fill based on water consumed Generate an array of boolean values representing filled/unfilled blocks Track when the last drink was taken These values are perfect for $derived because they: Are pure calculations based on state Need to update automatically when dependencies change Don't perform side effects Understanding $effect $effect is for running side effects when reactive values change. Unlike $derived, it doesn't return a value - it performs actions. From our app: $effect(() => { // Persist data to localStorage whenever values change if (!firstRun) { localStorage.setItem( 'waterData', JSON.stringify({ waterConsumedSaved: waterConsumed, lastDrinkSaved: lastDrink, dailyGoalSaved: dailyGoal }) ); } else { const waterData = localStorage.getItem('waterData'); if (waterData) { const { waterConsumedSaved, lastDrinkSaved, dailyGoalSaved } = JSON.parse(waterData); waterConsumed = waterConsumedSaved; lastDrink = lastDrinkSaved ? new Date(lastDrinkSaved) : null; dailyGoal = dailyGoalSaved; } firstRun = false; } }); This $effect is perfect for what it's doing: Persisting data to localStorage (side effect) Loading data on first run (side effect) Tracking when state changes to trigger these operations When to Use $effect (Sparingly) $effect should be used rarely and only for side effects. Common appropriate uses include: Persisting data to localStorage (as in our example) Making API calls when dependencies change Interacting with the DOM in ways not handled by Svelte's templating Logging or analytics Common $effect Misuses Here are examples of misusing $effect that should be avoided: Misuse 1: Computing values that should be $derived // BAD let doubledWater = 0; $effect(() => { doubledWater = waterConsumed * 2; }); // GOOD const doubledWater = $derived(waterConsumed * 2); Misuse 2: Conditional side effects that could be inline // BAD $effect(() => { if (waterConsumed >= dailyGoal) { console.log('Goal reached!'); } }); // GOOD - In template {#if waterConsumed >= dailyGoal} console.log('Goal reached!')}>Goal reached! {/if} Misuse 3: Overusing for simple DOM manipulations // BAD $effect(() => { document.title = `Water: ${waterConsumed}ml`; }); // GOOD - Use Svelte's component instead Conclusion Understanding the difference between $derived and $effect is crucial: $derived is for computing values based on state (pure, returns a value) $effect is for running side effects when state changes (impure, no return value) By using the right rune for the job, your Svelte 5 applications will be more maintainable, predictable, and efficient. Our water tracker app demonstrates how these runes can work together to create a responsive, state-driven application.

Svelte 5 introduces a powerful new reactive primitive system called "runes" that simplifies state management. Let's explore what runes are and dive deep into two important ones: $derived
and $effect
.
To demonstrate how to use these runes we will use a water tracker application that I've built. You can find the source code here: https://github.com/mikhail-karan/water-tracker-svelte5
What are Svelte 5 Runes?
Runes are special primitives in Svelte 5 marked with a $
prefix that enable fine-grained reactivity. Unlike Svelte 4's compiler-based reactivity, runes bring reactivity directly into the JavaScript runtime, making it more explicit and portable.
The main runes include:
-
$state
- Declares reactive state -
$derived
- Computes values from state -
$effect
- Runs side effects when dependencies change -
$props
- Handles component props
The Power of $derived
$derived
is used to compute values that depend on reactive state. It automatically updates whenever its dependencies change, and importantly, it's memoized (calculated only when needed).
From our water tracker app:
const blocksToFill = $derived(Math.min(Math.floor(waterConsumed / 100), 20));
const waterBlocks = $derived(
Array(20)
.fill(false)
.map((_, index) => index < blocksToFill)
); // 20 blocks representing 100ml each
const lastDrinkDerived = $derived(waterConsumed > 0 ? new Date() : null);
Here we use $derived
to:
- Calculate how many water blocks to fill based on water consumed
- Generate an array of boolean values representing filled/unfilled blocks
- Track when the last drink was taken
These values are perfect for $derived
because they:
- Are pure calculations based on state
- Need to update automatically when dependencies change
- Don't perform side effects
Understanding $effect
$effect
is for running side effects when reactive values change. Unlike $derived
, it doesn't return a value - it performs actions.
From our app:
$effect(() => {
// Persist data to localStorage whenever values change
if (!firstRun) {
localStorage.setItem(
'waterData',
JSON.stringify({
waterConsumedSaved: waterConsumed,
lastDrinkSaved: lastDrink,
dailyGoalSaved: dailyGoal
})
);
} else {
const waterData = localStorage.getItem('waterData');
if (waterData) {
const { waterConsumedSaved, lastDrinkSaved, dailyGoalSaved } = JSON.parse(waterData);
waterConsumed = waterConsumedSaved;
lastDrink = lastDrinkSaved ? new Date(lastDrinkSaved) : null;
dailyGoal = dailyGoalSaved;
}
firstRun = false;
}
});
This $effect
is perfect for what it's doing:
- Persisting data to localStorage (side effect)
- Loading data on first run (side effect)
- Tracking when state changes to trigger these operations
When to Use $effect (Sparingly)
$effect
should be used rarely and only for side effects. Common appropriate uses include:
- Persisting data to localStorage (as in our example)
- Making API calls when dependencies change
- Interacting with the DOM in ways not handled by Svelte's templating
- Logging or analytics
Common $effect Misuses
Here are examples of misusing $effect
that should be avoided:
Misuse 1: Computing values that should be $derived
// BAD
let doubledWater = 0;
$effect(() => {
doubledWater = waterConsumed * 2;
});
// GOOD
const doubledWater = $derived(waterConsumed * 2);
Misuse 2: Conditional side effects that could be inline
// BAD
$effect(() => {
if (waterConsumed >= dailyGoal) {
console.log('Goal reached!');
}
});
// GOOD - In template
{#if waterConsumed >= dailyGoal}
<div on:mount={() => console.log('Goal reached!')}>Goal reached!</div>
{/if}
Misuse 3: Overusing for simple DOM manipulations
// BAD
$effect(() => {
document.title = `Water: ${waterConsumed}ml`;
});
// GOOD - Use Svelte's component instead
Conclusion
Understanding the difference between $derived
and $effect
is crucial:
-
$derived
is for computing values based on state (pure, returns a value) -
$effect
is for running side effects when state changes (impure, no return value)
By using the right rune for the job, your Svelte 5 applications will be more maintainable, predictable, and efficient. Our water tracker app demonstrates how these runes can work together to create a responsive, state-driven application.