One of the most powerful features in Svelte 5’s new reactivity system is deep reactivity. At first glance, it feels magical: wrap an object in state(), and suddenly every nested property, array, and child object becomes reactive. But under the hood, how does Svelte achieve this? And what happens to the original object when you start mutating the state?
Let’s unpack what’s really going on.
Wrapping Objects in State
When you wrap an object with state, Svelte doesn’t create a deep copy of that object. Instead, it uses JavaScript proxies. These proxies intercept reads and writes, letting Svelte:
- Track dependencies for reactivity.
- React to changes at any depth of the object tree.
- Keep the original object untouched.
This means that if you mutate the object through the state reference, the changes are captured inside the proxy, not applied to the original object.
<script>
let original = { count: 0 };
let $counter = $state(original);
$counter.count++; // Mutates the proxy
console.log(original.count); // Still 0
</script>
Here, original stays the same (0) while the stateful proxy shows the updated value (1).
Deep Reactivity Without Deep Copying
A common misconception is that Svelte must be doing some kind of deep cloning when it makes objects reactive. That would be expensive and impractical, especially for large objects.
Instead, Svelte wraps the object lazily: when you access nested properties, it wraps them in their own proxies on demand. This is how it achieves deep reactivity without traversing the entire object tree upfront.
So while it feels like your entire object graph is cloned, under the hood, it’s just a clever network of proxies.
Snapshots: Turning Proxies Back into Objects
Sometimes, you’ll want a real object rather than a reactive proxy—for example, when sending data over the network. That’s where snapshots come in.
When you call a snapshot on a state, Svelte materialises a plain JavaScript object from the proxy’s current tracked state:
<script>
let $user = $state({ name: "Alice", age: 25 });
$user.age = 26;
let snapshot = $user[$snapshot];
console.log(snapshot); // { name: "Alice", age: 26 }
</script>
Snapshots are copies of the proxy’s current values—a frozen-in-time representation that won’t change if the proxy changes later.
Why This Matters
Understanding how Svelte handles state internally helps you avoid confusion when working with objects:
- Don’t expect mutations in a state proxy to reflect in your original object.
- Don’t worry about performance overhead from deep cloning—Svelte uses proxies, not copies.
- Use snapshots when you need a plain object (e.g., for APIs or serialisation).
In short: Svelte’s state system gives you deep reactivity for free, without sacrificing performance or data integrity.