A
Ali Aslam
Guest
One of the biggest reasons developers fall in love with Svelte is its reactivity model.
Unlike frameworks like React (where you have to call
By the end of this article, you’ll know:
Let’s start simple.
Last time (in previous article), we built a counter and it “just worked.” But why? Why does Svelte magically know to update your UI when you increment a variable?
That’s the heart of Svelte’s reactivity model — and to really “get” it, we’re going to zoom in on this tiny example and unpack every moving part.
Here’s the minimal version again:
At first glance, this looks like a regular variable assignment. But the
Without
Pro tip: If you’re a JavaScript developer, you can stay in your comfort zone with let count = 0;. Today, all variables are tracked. But Svelte is nudging us toward $state to make reactive variables explicit, and that flexibility may go away in the future. It’s worth getting used to $state now.
This is where the magic starts. The curly braces (
Behind the scenes, the compiler doesn’t just “print the number once.” It generates code that reassigns the text whenever
Here’s the shocking part: you didn’t call
But because it’s a
If you’ve used React, Vue, or Angular, you might be blinking at your screen right now. “Wait, that’s it?”
Yes, that’s it. No special function calls. No
Svelte takes care of it ahead of time during compilation. That’s why you can write code that feels like plain JavaScript but behaves like a reactive UI framework. Even $state is optional today — until it’s not.
Let’s stress this point again, because it’s the foundation of everything:
When you reassign
That’s the entire idea of reactivity:
If you’ve dabbled in React (or seen enough code samples online), you know how state updates look there:
Why the ceremony? Because React can’t just watch plain variables. Instead, it invented the virtual DOM.
The virtual DOM was clever: it kept a lightweight copy of your UI in memory, recalculated it whenever state changed, and then compared it with the real DOM to apply minimal updates. This solved a real problem in 2013: keeping UIs in sync with fast-changing data.
But… it came with runtime costs. React does the bookkeeping while your app is running.
Svelte takes a different route: it’s a compiler. At build time, it analyzes your code and generates direct DOM updates like:
So instead of building and diffing trees in memory, your compiled app just updates the DOM directly. The result: smaller bundles, faster runtime, and predictable updates.
Key takeaway: In Svelte, plain variable assignments are reactive because the compiler turns them into optimized DOM updates. That’s why
You might wonder: if compile-time is so great, why didn’t React do that?
Fast-forward to today:
So far, reactivity has been about: “the UI reacts when a variable changes.”
But what if another variable depends on that one?
That’s where derived reactivity comes in.
Here’s what’s happening:
This is a big mental shift. In other frameworks, you often need to tell the system “recalculate this when these variables change.” In Svelte, you just write the formula. The compiler figures out the dependencies.
Together, these two steps give you the foundation:
This mental model scales up beautifully — from simple counters to complex apps.
In every
Variables in
It’s just you, JavaScript, and some very helpful magic.
Sometimes you don’t just want to track state — you want to compute new values from it. For example, given a
That’s where
Think of
So here:
You could!
That works fine for one-off expressions. But
Takeaway:
Quick check-in: Are you typing out the code and trying it out?
Or at the very least copy-pasting it into your project?
If not, I’d highly encourage you to do it. Seeing the UI update while you tweak values will make these concepts click way faster than just reading about them.
Here’s where many beginners hit their first “Wait, why didn’t that update?” moment.
Try this:
Click the button… nothing happens.
This comes down to how Svelte’s reactivity works:
When you do:
…the array is modified in place. The variable
So from its perspective, no update happened.
This reflects a principle you may have heard of: immutability. In many reactive frameworks, instead of mutating arrays/objects directly, you create a new array/object with the updated value. That’s what Svelte expects if you’re using plain variables.
Instead of mutating, you give
What’s with the ...?
That’s the spread operator in JavaScript. It’s like saying:
“Take everything inside this array, and unpack it into a new one.”
So if numbers = [1, 2, 3], then:
[...numbers, 4]
becomes:
[1, 2, 3, 4]
You end up with a brand-new array that contains all the old values plus the new one.
This matters because now numbers has been reassigned to a fresh value (at a new memory location) — and that triggers Svelte’s reactivity.
Of course, constantly cloning arrays feels clunky. This is where
Click the button → it works
Because
Takeaway:
Arrays aren’t the only data structures we need in our apps — objects show up everywhere too. And just like arrays, objects have their own “gotcha” moments when it comes to reactivity.
Let’s start with a basic example:
Now click the button. You’ll see age of Alice go up immediately
.
As we saw earlier with array example, mutations to
That’s no longer necessary when the object is wrapped in
Key takeaway: When your state is wrapped in
Earlier in Step 3, we saw how you can branch out from one piece of state into multiple derived values (like
But what if one derived value should depend on another? That’s chained reactivity.
And you didn’t wire anything manually. Because you declared the relationships with
Both are powerful patterns, and together they cover most situations where one piece of data drives many others.
Takeaway:
So far, our reactive code has mostly been about assignments: “this value depends on that one.” But sometimes you want to run a block of code whenever certain variables change. That’s where
So if you click Increment, you’ll see new logs (and maybe an alert). If you click Change Name, you’ll also see logs. Both variables drive the same block.
Good catch: the UI here looks super bare. That’s on purpose.
Instead,
So here we log to the console just so you can see the reactivity happening. In a real app, you’d use
If you’ve never used
Takeaway: Use
Reactive blocks (
Instead:
This keeps your reactivity system lean and avoids slowing down the UI.
With this one example, you see both:
Reactive blocks let you express logic declaratively without worrying about wiring dependencies.
So far, we’ve been dealing with plain state — variables you update directly. But what about values that should always be calculated from other values?
That’s where derived values come in.
Let’s try building a little shopping scenario:
Go ahead and click the buttons.
Why? Because
To keep
Now click the buttons again:
If you’ve ever used Excel or Google Sheets, this will feel familiar:
Svelte does the same with
You might wonder: “But plain
Here’s the difference:
That’s where
“Don’t treat this as a one-off assignment, treat it like a formula that depends on other state. Keep it up to date for me.”
And this is the bigger lesson: explicit markers like
It’s like putting a bright sticky note on your code saying:
That clarity becomes priceless as your app grows.
One important rule: keep your dependencies flowing in one direction. If you try to make them loop back on each other, you’ll end up in an infinite loop.
Bad idea:
As long as data flows downstream — no circles — you’re safe.
Back in Step 6, we looked at
Takeaway:
So far, our reactivity examples have been instant: change a number, see the DOM update right away. But real apps often deal with data that takes time to arrive — maybe from a server or an API.
The great news: Svelte’s reactivity works just as smoothly with asynchronous code. Let’s see how.
Let’s make a page that looks up GitHub user profiles. We’ll start the old-fashioned way: click a button to fetch.
This gives you instant feedback: type, click, see results.
What’s happening here:
The button works, but we can go one step further. Instead of saying “fetch only when I click,” why not just say “fetch whenever
Here’s the magic:
You don’t have to list dependencies manually. The compiler does the wiring.
Async work can overlap. If you type
For now, don’t worry — just be aware this can happen. Later we’ll explore solutions like
Takeaway: Async reactivity works the same as everything else you’ve learned. Assign to a reactive variable — whether instantly or after a fetch — and the UI reacts.
Step 10: Common Gotchas
By now you’ve seen how smooth reactivity feels in Svelte. But every magic trick has a few “hidden wires” — little details that can trip you up if you don’t know they’re there. Let’s walk through the most common mistakes beginners run into (and how to dodge them).
1. Forgetting
The #1 pitfall: declaring state as a plain variable and forgetting to mark it as tracked.
This often still works today — Svelte tries to track plain variables — but it’s not the recommended approach.
When you write:
…you’re telling Svelte, “this variable belongs to the reactive system.” It removes ambiguity and makes it clear to future-you (and teammates) that this drives the UI.
Bottom line:
If you’ve skimmed older tutorials, you might have read:
That used to be true. But with
Example with an array:
And with an object:
Both update instantly. No more cloning arrays or spreading objects just to trigger updates.
Takeaway:
3. Overstuffing
It’s tempting to throw everything into one
This works, but it’s messy — you lose track of what depends on what.
A cleaner way is to use
Now each relationship is self-contained and obvious.
Guideline:
In React, state updates are batched and applied later. In Svelte, reactivity is synchronous: every assignment immediately re-runs the reactive graph.
Click the button → console shows:
Each
That might feel unusual if you’re coming from React, but it keeps Svelte’s model simple and predictable: assignment = update, immediately.
Step 11: Hands-On Recap
We’ve seen
This little app touches almost everything we’ve learned:
Try modifying this cart:
Every tweak reinforces the same truth: Svelte’s reactivity is just variables + assignments. The compiler does the heavy lifting.
You’ve just completed a full tour of Svelte’s reactivity system — the beating heart of how apps stay in sync with your data. From the humble
No extra libraries. No boilerplate. No “magic incantations.” Just variables → DOM.
Here’s the mental toolkit you now carry with you:
This was just the foundation. In the next article, we’ll explore how reactivity flows across components:
If reactivity is the heartbeat of a Svelte app, components are the organs — each with its own job, working together to bring your app to life.
So take a breather, celebrate what you’ve built today
, and when you’re ready, let’s continue the journey.
Next (Coming up): Svelte Components Explained: Props & Composition Made Simple.
Continue reading...
Unlike frameworks like React (where you have to call
setState
or use hooks), in Svelte you just… assign to a variable, and the UI updates. No ceremony. No boilerplate. Just magic. 
By the end of this article, you’ll know:
- How Svelte tracks reactive state.
- What reactive declarations are.
- How auto-updating works (and when it doesn’t).
- Gotchas you should watch out for.
Let’s start simple.
Step 1: Your First Reactive Variable
Last time (in previous article), we built a counter and it “just worked.” But why? Why does Svelte magically know to update your UI when you increment a variable?
That’s the heart of Svelte’s reactivity model — and to really “get” it, we’re going to zoom in on this tiny example and unpack every moving part.
Here’s the minimal version again:
Code:
src/routes/+page.svelte
Code:
<script>
let count = $state(0);
</script>
<h1>Count: {count}</h1>
<button on:click={() => count++}>Increment</button>
Let’s break this down, line by line
1. Declaring reactive state
Code:
let count = $state(0);
At first glance, this looks like a regular variable assignment. But the
$state
wrapper is your way of telling Svelte:“Hey, compiler — track this value. Whenever it changes, make sure the UI is updated.”
Without
$state
, count
would be just another local variable in your script. With $state
, it becomes reactive state, tied directly to your markup.
2. Referencing in markup
Code:
<h1>Count: {count}</h1>
This is where the magic starts. The curly braces (
{}
) tell Svelte:“Insert the current value ofcount
here. And if it ever changes, update this part of the DOM.”
Behind the scenes, the compiler doesn’t just “print the number once.” It generates code that reassigns the text whenever
count
updates. That means your <h1>
stays in sync without you lifting a finger.3. Updating the state
Code:
count++;
Here’s the shocking part: you didn’t call
setCount
, or this.setState
, or dispatch
. You just incremented a variable like you would in vanilla JavaScript.But because it’s a
$state
variable, Svelte’s compiled code knows to re-render anything that depends on it. That’s reactivity in action.
Why this feels different
If you’ve used React, Vue, or Angular, you might be blinking at your screen right now. “Wait, that’s it?”
Yes, that’s it. No special function calls. No
useState
. No dependency arrays. No magical “diffing algorithm” running at runtime.Svelte takes care of it ahead of time during compilation. That’s why you can write code that feels like plain JavaScript but behaves like a reactive UI framework. Even $state is optional today — until it’s not.
Assignments Trigger Reactivity
Let’s stress this point again, because it’s the foundation of everything:
When you reassign
count
, Svelte knows it changed and re-renders anything that depends on it ({count}
in this case).That’s the entire idea of reactivity:
- Variables you declare in
<script>
become reactive when marked with$state
. - Any time you assign to them, the DOM updates automatically.
Why this feels different from React
If you’ve dabbled in React (or seen enough code samples online), you know how state updates look there:
Code:
// React
const [count, setCount] = useState(0);
<button onClick={() => setCount(count + 1)}>Increment</button>
Why the ceremony? Because React can’t just watch plain variables. Instead, it invented the virtual DOM.
The virtual DOM was clever: it kept a lightweight copy of your UI in memory, recalculated it whenever state changed, and then compared it with the real DOM to apply minimal updates. This solved a real problem in 2013: keeping UIs in sync with fast-changing data.
But… it came with runtime costs. React does the bookkeeping while your app is running.
Svelte takes a different route: it’s a compiler. At build time, it analyzes your code and generates direct DOM updates like:
Code:
countElement.textContent = count;
So instead of building and diffing trees in memory, your compiled app just updates the DOM directly. The result: smaller bundles, faster runtime, and predictable updates.

count++
just works.�️ Why React chose runtime (and Svelte can do compile-time)
You might wonder: if compile-time is so great, why didn’t React do that?
- React’s goal (2013) → be a drop-in UI library, no build tools required. Runtime logic made it portable.
- Tooling back then → modern compilers and bundlers (like Vite, Rollup, esbuild) didn’t exist or weren’t widely used. A compiler-first approach would’ve been too hard to adopt.
- The result → the virtual DOM was the best runtime solution for its era.
Fast-forward to today:
- Developers are used to build steps.
- Tooling is mature.
- That’s why Svelte can move the heavy lifting to compile-time and ditch the runtime tax.
Step 2: Derived Reactivity
So far, reactivity has been about: “the UI reacts when a variable changes.”
But what if another variable depends on that one?
That’s where derived reactivity comes in.
Code:
src/routes/+page.svelte
Code:
<script>
let count = $state(0);
let doubled = $derived(count * 2);
</script>
<h1>Count: {count}</h1>
<h2>Doubled: {doubled}</h2>
<button on:click={() => count++}>Increment</button>
Here’s what’s happening:
doubled
is automatically recalculated whenevercount
changes.- When you click the button, both
<h1>
and<h2>
update instantly. - No hooks, no dependency arrays, no
useMemo
. Just declare the relationship, and Svelte takes care of the rest.
This is a big mental shift. In other frameworks, you often need to tell the system “recalculate this when these variables change.” In Svelte, you just write the formula. The compiler figures out the dependencies.

- Step 1 → state-driven reactivity (variables directly update the UI).
- Step 2 → derived reactivity (variables that depend on other variables).
This mental model scales up beautifully — from simple counters to complex apps.
A quick note on <script>
in Svelte
In every
.svelte
file, you can have three main sections:<script>
→ holds your JavaScript (variables, imports, logic).- Markup (HTML) → your UI structure.
<style>
→ component-scoped CSS.
Variables in
<script>
that you mark as $state
are directly tied to your UI.It’s just you, JavaScript, and some very helpful magic.
Step 3: Multiple Derived Values
Sometimes you don’t just want to track state — you want to compute new values from it. For example, given a
count
, you might want to display both its double and its square.That’s where
$derived
comes in.
Code:
src/routes/+page.svelte
Code:
<script>
let count = $state(2);
let doubled = $derived(count * 2);
let squared = $derived(count * count);
</script>
<h1>Count: {count}</h1>
<h2>Doubled: {doubled}</h2>
<h2>Squared: {squared}</h2>
<button on:click={() => count++}>Increment</button>
What $derived
really means
Think of
$derived
as a live formula. You’re telling Svelte:“This value always equals the result of this expression. Whenever its inputs change, update it.”
So here:
- When
count
changes,doubled
andsquared
recalculate automatically. - No extra function calls. No worrying about stale values.
Why not just write {count * 2}
inline?
You could!
Code:
<h2>Doubled: {count * 2}</h2>
That works fine for one-off expressions. But
$derived
shines when:- You need to reuse the computed value in multiple places (e.g. both in markup and in a script).
- The computation isn’t trivial and repeating it everywhere would be messy.
- You want to build on it further (hint: that’s Step 6).

$derived
gives you clean, reusable, always-up-to-date values. It’s a way of saying “don’t just track state — also track relationships between state.”. This concept is also known as declarative reactivity — you declare relationships, and Svelte figures out when to update them.
Or at the very least copy-pasting it into your project?
If not, I’d highly encourage you to do it. Seeing the UI update while you tweak values will make these concepts click way faster than just reading about them.
Step 4: Arrays and Reactivity
Here’s where many beginners hit their first “Wait, why didn’t that update?” moment.
Try this:
Code:
src/routes/+page.svelte
Code:
<script>
let numbers = [1, 2, 3];
function addNumber() {
numbers.push(numbers.length + 1);
}
</script>
<h1>Numbers: {numbers.join(', ')}</h1>
<button on:click={addNumber}>Add Number</button>
Click the button… nothing happens.

Why didn’t it update?
This comes down to how Svelte’s reactivity works:
- Assignments trigger updates.
- Mutations do not.
When you do:
Code:
numbers.push(4);
…the array is modified in place. The variable
numbers
itself didn’t change — it still points to the same array. Svelte’s compiler is watching for assignments like:
Code:
numbers = [...numbers, 4];
So from its perspective, no update happened.
This reflects a principle you may have heard of: immutability. In many reactive frameworks, instead of mutating arrays/objects directly, you create a new array/object with the updated value. That’s what Svelte expects if you’re using plain variables.
The Fix: Reassign the Array
Instead of mutating, you give
numbers
a brand-new value:
Code:
function addNumber() {
numbers = [...numbers, numbers.length + 1];
}

That’s the spread operator in JavaScript. It’s like saying:
“Take everything inside this array, and unpack it into a new one.”
So if numbers = [1, 2, 3], then:
[...numbers, 4]
becomes:
[1, 2, 3, 4]
You end up with a brand-new array that contains all the old values plus the new one.
This matters because now numbers has been reassigned to a fresh value (at a new memory location) — and that triggers Svelte’s reactivity.
The Better Way: Use $state
Of course, constantly cloning arrays feels clunky. This is where
$state
comes in handy:
Code:
src/routes/+page.svelte
Code:
<script>
let numbers = $state([1, 2, 3]);
function addNumber() {
numbers.push(numbers.length + 1); // mutation is tracked
}
</script>
<h1>Numbers: {numbers.join(', ')}</h1>
<button on:click={addNumber}>Add Number</button>
Click the button → it works

Because
$state
tells Svelte to fully track the variable, even mutations like push
, pop
, or splice
will update the DOM.
- With plain arrays, Svelte only reacts to reassignments — think in terms of immutability.
- With
$state
, mutations are tracked too, so arrays behave naturally.
Step 5: Reactivity with Objects
Arrays aren’t the only data structures we need in our apps — objects show up everywhere too. And just like arrays, objects have their own “gotcha” moments when it comes to reactivity.
Let’s start with a basic example:
Code:
src/routes/+page.svelte
Code:
<script>
let user = $state({ name: "Alice", age: 30 });
function incrementAge() {
user.age += 1;
}
</script>
<p>{user.name} is {user.age} years old.</p>
<button on:click={incrementAge}>Happy Birthday!</button>
Now click the button. You’ll see age of Alice go up immediately

As we saw earlier with array example, mutations to
$state
objects are also automatically tracked. You don’t need to reassign a copy of the object every time. In older patterns, you might have had to do something like:
Code:
user = { ...user, age: user.age + 1 };
That’s no longer necessary when the object is wrapped in
$state
. Just update it directly, and the DOM reflects the change.
$state
, both arrays and objects are fully reactive — mutations are tracked, no extra ceremony required.Step 6: Chained Reactivity
Earlier in Step 3, we saw how you can branch out from one piece of state into multiple derived values (like
doubled
and squared
). That was parallel derivation — multiple values depending directly on the same source.But what if one derived value should depend on another? That’s chained reactivity.
Code:
src/routes/+page.svelte
Code:
<script>
let price = $state(100);
// tax depends on price
let tax = $derived(price * 0.1);
// total depends on both price and tax
let total = $derived(price + tax);
</script>
<h1>Price: ${price}</h1>
<h2>Tax: ${tax}</h2>
<h2>Total: ${total}</h2>
<button on:click={() => price += 10}>Increase Price</button>
How it flows
- When
price
changes →tax
recalculates. - When
tax
changes →total
recalculates. - The DOM stays perfectly in sync.
And you didn’t wire anything manually. Because you declared the relationships with
$derived
, Svelte’s compiler knows the dependency graph:
Code:
price → tax → total
Contrast with Step 3
- Step 3 (multiple derived values): one source → multiple independent branches (
count → doubled
andcount → squared
). - Step 6 (chained reactivity): one source → values feeding into each other (
price → tax → total
).
Both are powerful patterns, and together they cover most situations where one piece of data drives many others.

$derived
doesn’t just save you from repeating calculations — it gives you a declarative way to model data dependencies. Whether branching or chaining, the compiler takes care of the wiring so you can focus on the logic.Step 7: Reactive Blocks
So far, our reactive code has mostly been about assignments: “this value depends on that one.” But sometimes you want to run a block of code whenever certain variables change. That’s where
$effect
comes in.
Code:
src/routes/+page.svelte
Code:
<script>
let count = $state(0);
let name = $state("Alice");
$effect(() => {
console.log(`Count is ${count}, name is ${name}`);
if (count > 5) {
alert("Whoa! Count is big now 🚀");
}
});
</script>
<button on:click={() => count++}>Increment</button>
<button on:click={() => name = "Bob"}>Change Name</button>
How it works
- Svelte looks at all variables used inside the effect (
count
andname
here). - Whenever any of them changes, the whole block re-runs.
- You don’t have to list dependencies yourself — Svelte figures it out.
So if you click Increment, you’ll see new logs (and maybe an alert). If you click Change Name, you’ll also see logs. Both variables drive the same block.
Why console logs instead of UI updates?
Good catch: the UI here looks super bare. That’s on purpose.
$effect
is not usually for rendering things in the DOM — that’s what normal markup and $state
/$derived
variables are for.Instead,
$effect
is meant for side effects:- writing to the console (for debugging),
- showing an alert,
- syncing to localStorage,
- kicking off a network request,
- starting or stopping an animation.
So here we log to the console just so you can see the reactivity happening. In a real app, you’d use
$effect
for those “do something extra when state changes” situations.
console.log
before: right-click your page, choose Inspect, and open the Console tab. That’s where the messages will appear.
$effect
when you need to react to changes with side effects — not for normal UI updates.
Gotcha: Don’t overload reactive blocks
Reactive blocks (
$effect
) are incredibly useful, but they re-run every time a dependency changes. That means you should avoid doing heavy work (like big loops or API calls) directly inside them.Instead:
- Use reactive blocks for lightweight side effects (logging, triggering an animation, simple checks).
- For heavier tasks, call a helper function from inside the block.
This keeps your reactivity system lean and avoids slowing down the UI.

- a single dependency (
count
driving the alert), and - multiple dependencies (
count
+name
in the console).
Reactive blocks let you express logic declaratively without worrying about wiring dependencies.
Step 8: Derived Values (a.k.a Computed Values)
So far, we’ve been dealing with plain state — variables you update directly. But what about values that should always be calculated from other values?
That’s where derived values come in.
The Problem: Calculated Once, Then Stale
Let’s try building a little shopping scenario:
Code:
src/routes/+page.svelte
Code:
<script>
let price = $state(20);
let quantity = $state(3);
// ❌ Only calculated once, not reactive
let total = price * quantity;
</script>
<p>Price: ${price}</p>
<p>Quantity: {quantity}</p>
<p>Total: ${total}</p>
<button on:click={() => price += 5}>Increase Price</button>
<button on:click={() => quantity++}>Increase Quantity</button>
Go ahead and click the buttons.
price
and quantity
change as expected… but total
stays frozen at the original calculation.Why? Because
total = price * quantity
ran only once when the component loaded. It’s just a plain JavaScript assignment — Svelte isn’t tracking it.
The Fix: $derived
To keep
total
in sync automatically, declare it as a derived value:
Code:
src/routes/+page.svelte
Code:
<script>
let price = $state(20);
let quantity = $state(3);
// ✅ Always recalculates when price or quantity changes
let total = $derived(price * quantity);
</script>
<p>Price: ${price}</p>
<p>Quantity: {quantity}</p>
<p>Total: ${total}</p>
<button on:click={() => price += 5}>Increase Price</button>
<button on:click={() => quantity++}>Increase Quantity</button>
Now click the buttons again:
total
updates instantly. No extra code, no manual recalculation — just a clear declaration of intent.
Mental Model: Think Spreadsheets
If you’ve ever used Excel or Google Sheets, this will feel familiar:
Code:
A1 = 20
A2 = 3
A3 = A1 * A2
- If you change A1 or A2, A3 updates automatically.
- You don’t press a “recalculate” button — the dependency is built in.
Svelte does the same with
$derived
. It builds a dependency graph during compilation and keeps everything fresh.
Counter vs. Derived: Two Flavors of Reactivity
You might wonder: “But plain
count++
worked earlier, so why not total
?”Here’s the difference:
- With
count
, you’re directly reassigning the variable (count = count + 1
) on button click. That reassignment is what Svelte’s compiler watches for, so the UI updates. - With
let total = price * quantity;
, you’re not reassigningtotal
after the first run. It’s just a one-time calculation that never changes.
That’s where
$derived
steps in. It tells Svelte:“Don’t treat this as a one-off assignment, treat it like a formula that depends on other state. Keep it up to date for me.”

$state
and $derived
are for your convenience, not Svelte’s.- Svelte could often “guess” which variables are reactive — and in fact, it still does if you forget.
- But when you use
$state
or$derived
, you’re making your intent obvious to future-you (and your teammates). No hidden magic, no guessing games.
It’s like putting a bright sticky note on your code saying:
- “This variable changes over time.” (
$state
) - “This variable is a formula, not a snapshot.” (
$derived
)
That clarity becomes priceless as your app grows.
Gotcha: Circular Dependencies
One important rule: keep your dependencies flowing in one direction. If you try to make them loop back on each other, you’ll end up in an infinite loop.
Bad idea:
Code:
<script>
let a = $derived(b + 1);
let b = $derived(a + 1); // 🚨 Infinite loop!
</script>
As long as data flows downstream — no circles — you’re safe.
Bonus: Chaining Derived Values
Back in Step 6, we looked at
price → tax → total
. That’s actually just chained derived values. Each one depends on the previous, and Svelte keeps the whole chain in sync without you lifting a finger.
$derived
is your tool for values that should always reflect other values. It makes your code cleaner, avoids stale state, and turns your app into a living spreadsheet.Step 9: Async Reactivity
So far, our reactivity examples have been instant: change a number, see the DOM update right away. But real apps often deal with data that takes time to arrive — maybe from a server or an API.
The great news: Svelte’s reactivity works just as smoothly with asynchronous code. Let’s see how.
First try: fetch on button click
Let’s make a page that looks up GitHub user profiles. We’ll start the old-fashioned way: click a button to fetch.
Code:
src/routes/+page.svelte
Code:
<script>
let username = $state("octocat");
let userData = $state(null);
async function loadUser() {
const res = await fetch(`https://api.github.com/users/${username}`);
userData = await res.json();
}
</script>
<input
value={username}
placeholder="GitHub username"
on:input={(e) => { username = e.target.value; }}
/>
<button on:click={loadUser}>Fetch User</button>
{#if userData}
<h2>{userData.name}</h2>
<img src={userData.avatar_url} width="100" />
{/if}
Try it yourself
- Open the page in your browser — you’ll see the input prefilled with
octocat
. - Click Fetch User. → You should see Octocat’s name and avatar pop up underneath.
- Now type another GitHub username (for example,
torvalds
orocto
) into the box. - Press the button again to fetch that profile.
This gives you instant feedback: type, click, see results.
What’s happening here:
- Typing in the input updates the
username
variable (on:input
is just likeon:click
, but for text fields). - Clicking the button calls
loadUser()
, which fetches JSON from GitHub. - When we assign the result to
userData
, the{#if userData}
block becomes true and shows the profile info.

{#if …}
is Svelte’s way of conditionally rendering content. If the expression is truthy, the block shows up; otherwise it stays hidden.
The reactive way: automatic fetching
The button works, but we can go one step further. Instead of saying “fetch only when I click,” why not just say “fetch whenever
username
changes”? That’s what $effect
is for.
Code:
src/routes/+page.svelte
Code:
<script>
let username = $state("octocat");
let userData = $state(null);
$effect(() => {
if (!username) {
userData = null;
return;
}
fetch(`https://api.github.com/users/${username}`)
.then(r => r.json())
.then(data => { userData = data; });
});
</script>
<input
placeholder="GitHub username"
on:input={(e) => { username = e.target.value; }}
/>
{#if userData}
<h2>{userData.name}</h2>
<img src={userData.avatar_url} width="100" />
{/if}
Here’s the magic:
$effect
automatically tracksusername
, since we use it inside.- Each time you type,
username
updates → the effect reruns → a new fetch is made. - When the response comes back, assigning to
userData
triggers the DOM to update.

A little warning: race conditions
Async work can overlap. If you type
octo
(slow response) and quickly change to octocat
(fast response), the slower one might finish later and overwrite the newer data. (Do try it out and see for yourself)For now, don’t worry — just be aware this can happen. Later we’ll explore solutions like
AbortController
, debouncing, or SvelteKit’s load
function.
Step 10: Common Gotchas
By now you’ve seen how smooth reactivity feels in Svelte. But every magic trick has a few “hidden wires” — little details that can trip you up if you don’t know they’re there. Let’s walk through the most common mistakes beginners run into (and how to dodge them).
1. Forgetting $state
The #1 pitfall: declaring state as a plain variable and forgetting to mark it as tracked.
Code:
<script>
let count = 0; // ❌ plain variable, not explicitly tracked
function increment() {
count++;
}
</script>
<h1>{count}</h1>
<button on:click={increment}>Increment</button>
This often still works today — Svelte tries to track plain variables — but it’s not the recommended approach.
When you write:
Code:
let count = $state(0);
…you’re telling Svelte, “this variable belongs to the reactive system.” It removes ambiguity and makes it clear to future-you (and teammates) that this drives the UI.

let count = 0;
works for now, but $state
is the safe, future-proof choice.2. Thinking mutations won’t update the UI
If you’ve skimmed older tutorials, you might have read:
“Svelte only reacts to reassignments, not mutations.”
That used to be true. But with
$state
, mutations are tracked.Example with an array:
Code:
<script>
let numbers = $state([1, 2, 3]);
function addNumber() {
numbers.push(numbers.length + 1); // ✅ tracked automatically
}
</script>
<h1>{numbers.join(', ')}</h1>
<button on:click={addNumber}>Add</button>
And with an object:
Code:
<script>
let user = $state({ name: "Alice", age: 30 });
function birthday() {
user.age++; // ✅ updates the UI
}
</script>
<p>{user.name} is {user.age} years old.</p>
<button on:click={birthday}>Happy Birthday!</button>
Both update instantly. No more cloning arrays or spreading objects just to trigger updates.

$state
makes mutations reactive. Forget $state
, and you’re back in the old world where only reassignments count.3. Overstuffing $effect
blocks
It’s tempting to throw everything into one
$effect
:
Code:
<script>
let a = $state(1);
let b = $state(2);
let c = $state(3);
$effect(() => {
console.log(a * 2);
console.log(b + 3);
console.log(c + 4);
});
</script>
This works, but it’s messy — you lose track of what depends on what.
A cleaner way is to use
$derived
for computed values:
Code:
<script>
let a = $state(1);
let b = $state(2);
let c = $state(3);
let doubled = $derived(a * 2);
let sum = $derived(b + 3);
let shifted = $derived(c + 4);
</script>
Now each relationship is self-contained and obvious.

- Use
$effect
for side effects (logging, fetching, imperative work). - Use
$derived
for calculations.
4. Expecting batching like in React
In React, state updates are batched and applied later. In Svelte, reactivity is synchronous: every assignment immediately re-runs the reactive graph.
Code:
<script>
let count = $state(0);
$effect(() => {
console.log("Count is", count);
});
function incrementTwice() {
count++;
count++;
}
</script>
<h1>{count}</h1>
<button on:click={incrementTwice}>Increment Twice</button>
Click the button → console shows:
Code:
Count is 1
Count is 2
Each
count++
triggers reactivity right away. Nothing is batched or deferred.
Step 11: Hands-On Recap
Shopping Cart Edition
We’ve seen
$state
, $derived
, $effect
, and conditional rendering in isolation. Now let’s combine them into a mini shopping cart app.
Code:
src/routes/+page.svelte
Code:
<script>
// State: tracked variables
let items = $state([]);
let newItem = $state("");
let taxRate = $state(0.1); // 10%
// Derived values: formulas that stay in sync
let itemCount = $derived(items.length);
let subtotal = $derived(items.reduce((sum, item) => sum + item.price, 0));
let tax = $derived(subtotal * taxRate);
let total = $derived(subtotal + tax);
// Add a new item (hardcoded price for demo)
function addItem() {
if (!newItem.trim()) return;
items.push({ name: newItem, price: 10 });
newItem = "";
}
// Side effect: alert if cart gets big
$effect(() => {
if (itemCount > 5) {
alert("Whoa, that’s a big cart! 🛒");
}
});
</script>
<h1>My Cart</h1>
<input
placeholder="Add item"
value={newItem}
on:input={(e) => (newItem = e.target.value)}
/>
<button on:click={addItem}>Add</button>
{#if itemCount === 0}
<p>Your cart is empty. 🛍️</p>
{:else}
<ul>
{#each items as item}
<li>{item.name} - ${item.price}</li>
{/each}
</ul>
<p>Items: {itemCount}</p>
<p>Subtotal: ${subtotal}</p>
<p>Tax: ${tax.toFixed(2)}</p>
<h2>Total: ${total.toFixed(2)}</h2>
{/if}
What’s happening here?
This little app touches almost everything we’ve learned:
\$state
items
,newItem
, andtaxRate
are tracked.- Adding items updates the array reactively.
\$derived
itemCount
,subtotal
,tax
, andtotal
stay in sync withitems
andtaxRate
.- No manual recalculation needed — change one input, everything flows.
\$effect
- Whenever
itemCount
changes, the effect runs. If the cart grows too large, you get a playful alert.
- Whenever
Conditional rendering
{#if itemCount === 0}
shows “Cart is empty” until you add something.
Events
on:input
andon:click
wire up the UI to state updates.
Your Turn: Experiment!
Try modifying this cart:
- Change the hardcoded
price: 10
to let users type a price. - Add a “Remove” button next to each item (hint:
items.splice(index, 1)
). - Change the
taxRate
value and watchtax
+total
recalc automatically.
Every tweak reinforces the same truth: Svelte’s reactivity is just variables + assignments. The compiler does the heavy lifting.
Final Thoughts
You’ve just completed a full tour of Svelte’s reactivity system — the beating heart of how apps stay in sync with your data. From the humble
$state
counter, all the way to arrays, derived formulas, async fetches, and even a mini shopping cart app, you’ve seen how far you can get with nothing more than plain assignments and clear intent.No extra libraries. No boilerplate. No “magic incantations.” Just variables → DOM.
Here’s the mental toolkit you now carry with you:
$state
→ mark the variables that drive your UI.$derived
→ write formulas once, let them stay in sync forever.$effect
→ handle side effects like logging, fetching, or alerts.{#if}
,{#each}
→ let the DOM adapt automatically as your data changes.
Where We’re Headed Next
This was just the foundation. In the next article, we’ll explore how reactivity flows across components:
- Passing data down with props.
- Composing components together.
If reactivity is the heartbeat of a Svelte app, components are the organs — each with its own job, working together to bring your app to life.
So take a breather, celebrate what you’ve built today

Next (Coming up): Svelte Components Explained: Props & Composition Made Simple.
Continue reading...