Choosing Between Svelte Writable, Readable, and Derived Stores
Learn when to use each Svelte store type, see a comparison table, and follow a concrete example that shows how to create, update, and validate them.
11 Aug 2025, 19:25 UTC

Decision: Which store fits your state needs?
When building a Svelte application you often need to share state between components. Svelte provides three primitive store types that differ in who can change the value and how the value is derived. The decision hinges on three constraints:
- Mutability – does the value need to be changed from outside the store?
- Source of truth – is the value set internally (e.g., from a timer or websocket) or only via external calls?
- Derivation – does the value depend on one or more other stores?
Answering these questions points you to writable, readable, or derived stores.
Comparison of store types
| Store type | Can read? | Can write? | Typical use case |
|---|---|---|---|
| Writable | Yes (via subscribe or $store) | Yes (set, update) | Mutable UI state – form fields, counters, toggles |
| Readable | Yes (via subscribe or $store) | No – value set only inside the store’s start function | External streams – timers, WebSocket messages, constants |
| Derived | Yes (via subscribe or $store) | No – value computed automatically from source stores | Computed values – totals, filtered lists, derived flags |
Trade‑offs and pitfalls
Choosing the wrong store type can lead to extra boilerplate, unnecessary subscriptions, or stale data.
- Writable stores give you full control but require you to remember to use
setorupdate; mutating the result ofget()directly breaks reactivity. - Readable stores prevent accidental external writes, which is useful for data that should only change inside the store (e.g., a clock). The trade‑off is that you cannot update them from a component; you must drive changes from within the start function.
- Derived stores eliminate manual recalculation, but the derivation function runs synchronously on every source change. Performing asynchronous work inside that function blocks the update cycle and can cause lag; instead, fire side‑effects in a
tickcallback or a separate task.
Concrete implementation: a counter with a derived total and a ticking clock
The example below shows a writable store for a counter, a readable store that emits the current seconds, and a derived store that doubles the counter value. The component subscribes to all three stores using the $ prefix and updates the UI.
// src/stores.js
import { writable, readable, derived } from 'svelte/store';
// Writable store for a simple counter
export const count = writable(0);
// Readable store that ticks every second
export const tick = readable(0, function start(set) {
const interval = setInterval(() => {
set(Date.now());
}, 1000);
return () => clearInterval(interval);
});
// Derived store that computes double the count
export const doubleCount = derived(count, $count => $count * 2);
// src/App.svelte
Svelte Store Demo
Count: $count
Double count: $doubleCount
Clock (ms): $tick
count.update(n => n + 1)}>
Increment
To verify the behavior:
- Run the development server (
npm run devorpnpm dev) and open the app in a browser. - Click the “Increment” button and observe that Count and Double count increase while the clock continues to tick independently.
- Open the browser console; there should be no warnings about missing cleanup or stale subscriptions.
- Use the devtools to inspect the stores (if you have the Svelte devtools extension) and confirm that
doubleCountupdates only whencountchanges, not on every tick.
This example demonstrates the decision flow: the counter needed external mutation → writable; the clock needed an internal source with no external setter → readable; the doubled value needed a pure computation from another store → derived.
Limitations and practical checks
- The readable store’s internal setter (
set) is only available inside the start function; you cannot calltick.set()from a component. - Derived stores recompute synchronously; if you need to perform async work, launch it outside the derivation (e.g., in a
awaitblock or atickcallback) to avoid blocking the UI. - Always unsubscribe manually only if you created a custom subscription with
store.subscribe; the$prefix handles cleanup automatically.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.