$props() Destructuring and $bindable() Two-Way Bindings
- Last updated
- Prerequisites:
- Svelte 5 runes basics
- Understanding of props and bindings in Svelte 4
- svelte
- runes
- props
- bindable
- two-way-binding
- component-api
Read at your depth
The practical view
In Svelte 5, props are declared with $props(): let { name, age = 18 } = $props();. Destructuring gives you local variables that update when the parent changes them. For two-way binding, mark a prop $bindable(): let { count, onChange } = $props(); and in the parent <Child bind:count>. Alternatively: let { count = $bindable(0) } = $props(). The child can assign to count (count += 1) and the parent's variable updates. This replaces Svelte 4's export let + bind: syntax.
The same idea in other frameworks
Destructured props are read-only; two-way requires lifting state + callbacks — Svelte's $bindable is sugar for that.input() (read-only) + model() (two-way) mirror $props/$bindable.defineProps + defineModel in <script setup> are the direct analogues.Legacy vs modern
Svelte 4 export let + bind vs Svelte 5 $props/$bindable
Svelte 4's export let made all props technically writable (ambiguous two-way); $props()/$bindable() make the contract explicit.
export let count = 0; // ❌ OLD: props via export let
// any prop could be assigned internally — no explicit two-way markerlet { count = $bindable(0), label } = $props(); // ✅ NEW: explicit two-way markerInterview gotchas
Context
This tests the two-way binding contract — interviewers want to know the write-propagation semantics and the silent-divergence trap when the parent doesn't bind.
The mechanical answer
$bindable() declares that the child may write to the prop, establishing a two-way channel: the child's assignment compiles into a write that propagates back to the parent's state through the bind: mechanism. If the parent renders <Child count={n}> WITHOUT bind:, the child's write has nowhere to propagate — the write updates the child's local prop value, but the parent's n does not change, so the UI diverges silently until the parent re-renders. This is why $bindable() props should be used with bind: in the parent, or the child should treat the prop as a controlled value and emit changes via a callback instead. The compiler helps by marking bindable props distinctly, but the parent's usage determines whether writes actually flow.
Trap
The naive answer is '$bindable just lets the child change its prop'. Without bind:, the parent never sees the change — the classic controlled-component pitfall. Another trap: assuming bindable props must always be used two-way; a common pattern is to pair a bindable with a change callback for controlled usage. Candidates who explain the bind: propagation requirement show real understanding.
Context
This probes the compiler transformation — interviewers want to know how a destructured variable can remain a live binding rather than a one-time copy, which contradicts plain JS semantics.
The mechanical answer
The Svelte compiler does not let destructuring be a plain copy. It transforms each $props() destructured name into a reactive accessor: reading the name compiles to a read of the props signal, and (for bindable props) writing it compiles to a signal write. The component's template and effects read these accessors inside reactive contexts, so when the parent updates the prop, the props signal changes and the dependent reads re-run. The default-value fallbacks are applied lazily when the incoming value is undefined. This is why destructured props behave as live bindings rather than snapshots — the compiler rewrites the destructure into signal-backed accessors, preserving reactivity across the component boundary.
Trap
The naive answer is 'destructuring copies the value at mount'. The compiler replaces the copy with reactive accessors; reads track, writes propagate. Another trap: destructuring $props() inside a non-reactive context (e.g. an event handler called later) still works for reading, but reading it outside a component context is not a thing — the accessors are component-scoped. Candidates who describe the compiler rewrite into signal accessors show real Svelte 5 knowledge.