· frameworks · 5 min read
Unlocking the Power of Reactive Programming in Svelte
Master Svelte’s reactivity to build faster, simpler, and more maintainable web apps. Learn reactive declarations, stores, derived state, common pitfalls, and performance patterns with clear examples.

What you’ll be able to build
By the end of this article you’ll understand how to use Svelte’s reactivity model to reduce bugs, improve performance, and simplify state flow across components. You’ll know when to use reactive declarations, when to lift logic into stores, and how to avoid common pitfalls that silently break updates. Read on and you’ll write code that’s both faster and easier to change.
Why Svelte’s reactive model matters (quick answer)
Svelte compiles reactivity into efficient DOM updates. No virtual DOM. No runtime diffing. That means less code, fewer re-renders, and clearer data flow when you use Svelte’s reactive patterns correctly. Use them well and your app will be faster and easier to maintain. Misuse them, and you’ll get subtle bugs and wasted computation.
Core concept: reactive declarations ($:)
Svelte’s reactive declaration uses the $: label. It’s a simple but powerful tool: it reruns a piece of code whenever a dependency changes.
Example - derived value:
<script>
let count = 0;
// recalculated whenever `count` changes
$: double = count * 2;
</script>
<button on:click={() => count++}>Increment</button>
<p>{count} -> {double}</p>Notes:
$:can introduce new variables (likedouble) or run side effects (e.g., logging, fetching).- The reactivity system tracks variable reads inside
$:and automatically recomputes when they change.
Reactive statements vs. reactive assignments
There are two main forms you’ll use:
- Reactive assignment (introduces or updates a value):
$: total = price * quantity;- Reactive statement (pure side-effect):
$: {
console.log('total changed', total);
// or trigger some imperative code
}Important: reactive statements are executed in a deterministic order based on dependencies. Svelte builds a dependency graph at compile time and runs nodes in topological order so values settle predictably.
Stores: shared reactive state across components
When state must be shared between components, Svelte provides stores. The common variants are writable, readable, and derived.
// stores.js
import { writable, derived } from 'svelte/store';
export const todos = writable([]);
export const filter = writable('all');
export const visibleTodos = derived([todos, filter], ([$todos, $filter]) =>
$todos.filter(t => $filter === 'all' || t.status === $filter)
);In components you can auto-subscribe using the $ prefix:
<script>
import { visibleTodos } from './stores.js';
</script>
<ul>
{#each $visibleTodos as todo}
<li>{todo.text}</li>
{/each}
</ul>Key points:
$storeauto-subscribes and unsubscribes in the component.derivedavoids repeating logic across components and reduces DOM churn.
References: Svelte stores documentation: https://svelte.dev/docs#run-time-svelte-store
Custom stores: encapsulate behavior
For complex state with methods and lifecycle behavior create a custom store:
import { writable } from 'svelte/store';
export function createTimer() {
const { subscribe, set, update } = writable(0);
let interval;
return {
subscribe,
start: () => {
if (interval) return;
interval = setInterval(() => update(n => n + 1), 1000);
},
stop: () => {
clearInterval(interval);
interval = null;
},
reset: () => set(0),
};
}Use it in components like any store and call methods to control the behavior.
Gotchas and common pitfalls
- Mutation without assignment
Svelte tracks assignments, not deep mutations. Mutating arrays or objects without reassigning will not trigger updates.
<script>
let items = [];
items.push(1); // Svelte won't see this change
items = items; // forced update
// better: items = [...items, 1];
</script>- Reactive loops / infinite updates
A reactive statement that assigns to a variable it reads will re-run repeatedly:
$: count = count + 1; // BAD -> infinite loopAvoid self-referential reactive updates or guard them with conditions.
- Expensive computation re-run
Heavy calculations inside $: will run every time any dependency changes. Move expensive work into a derived store, memoize, or throttle.
- Subscribing manually
If you subscribe manually to a store inside a component, remember to unsubscribe in onDestroy.
Performance patterns - write reactive, optimized code
- Derive values in stores, not in multiple components. This reduces repeated calculations and centralizes logic.
- Keep reactive statements small. Let them compute a single value or perform a single side effect.
- Prefer immutable updates for arrays/objects to make changes obvious:
items = items.concat(newItem)oritems = items.map(...). - Debounce expensive effects (search queries, large computations).
- Use
tick()when you need to wait for DOM updates in an async flow.
Example - avoid re-running expensive work:
// BAD: expensive recalculation in component
$: result = expensiveCalculation(a, b, c);
// BETTER: derived store with memoization
import { derived } from 'svelte/store';
export const result = derived([aStore, bStore, cStore], ([$a, $b, $c]) => expensiveCalculation($a, $b, $c));Because derived centralizes the work, multiple components can reuse the result without repeating the heavy call.
Real-world example: search + filtering with reactive declarations
Imagine a list of users with filtering and a computed suggestion score. Implement reactive updates so UIs update only when necessary.
<script>
import { writable, derived } from 'svelte/store';
const query = writable('');
const users = writable([]);
// derived, memoized search results
const results = derived([users, query], ([$users, $query]) => {
const q = $query.trim().toLowerCase();
if (!q) return $users;
return $users.filter(u => u.name.toLowerCase().includes(q));
});
</script>
<input bind:value={$query} placeholder="Search users" />
{#each $results as user}
<div>{user.name}</div>
{/each}This approach:
- Keeps filtering logic in one place.
- Avoids recomputing in every component that renders the list.
- Lets Svelte efficiently update only affected DOM nodes.
Debugging reactivity
- Use console logs inside
$:to observe when reactive blocks run.
$: console.log('total recalculated', total);- Svelte Devtools show component state and store values. Install it for quicker inspection.
- Instrument stores with
subscribelogs during development to verify update frequency.
Reference: Svelte tutorial on reactive statements: https://svelte.dev/tutorial/reactive-statements
Best practices checklist
- Prefer small, single-purpose reactive statements.
- Move shared derived logic into stores.
- Avoid mutating arrays/objects; reassign to trigger updates.
- Guard reactive statements to prevent infinite loops.
- Put expensive work in derived stores or memoize it.
- Use the
$prefix for store reads in components to auto-subscribe. - Use Svelte Devtools to inspect update frequency.
Final thoughts
Svelte’s reactive model is deceptively simple and extremely powerful when used correctly. It lets you express data dependencies directly and rely on the compiler to generate efficient updates. The result is code that reads like intent and performs like optimized native code. Embrace reactive declarations for local logic, lift shared behavior into stores, and treat expensive computations with care - do that, and your app will be faster, clearer, and easier to maintain.
References
- Svelte Official Docs - Reactive Statements: https://svelte.dev/tutorial/reactive-statements
- Svelte Official Docs - Stores: https://svelte.dev/docs#run-time-svelte-store



