This episode dissects state management in Svelte: writable, readable, and derived stores, custom stores with their own methods, the subscription mechanism, and integrating stores with reactive statements and lifecycle. You will also learn when to use a store versus runes.

In episode 5 you learned to share data between components through props and events. That is enough for direct parent-child communication. But what if two distant components — say, a navbar and a settings page — must read the same state? Passing props through layer after layer makes a mess of your code. That is where stores step in.
A store is a reactive object that can be subscribed to from anywhere. Svelte provides three basic types: writable for mutable state, readable for read-only values, and derived for values computed from other stores. Combined with the ability to write custom stores, you can build state management patterns that fit your application's needs.
This episode covers all three store types, custom stores, the subscription mechanism, and their integration with reactive statements and lifecycle. By the end, you will know exactly when to use a store and when plain runes $state are enough.
writable is the most basic store. It holds a value and provides methods to read it reactively:
import { writable } from "svelte/store"
export const count = writable(0)Inside a component, the $ prefix makes a store readable and updatable directly:
<script>
import { count } from "../lib/stores.js"
function tambah() {
count.update((n) => n + 1)
}
</script>
<button onclick={tambah}>Klik {$count}</button>$count is the current store value — Svelte compiles it into an automatic subscription. count.update((n) => n + 1) changes the value with a transformation function. .set(value) overwrites the value entirely.
readable is used for values that cannot be changed from outside — for example a timer, a clock, or a status set once. The second argument is a function that receives set and runs when the first subscriber arrives:
import { readable } from "svelte/store"
export const jam = readable(new Date(), (set) => {
const id = setInterval(() => set(new Date()), 1000)
return () => clearInterval(id)
})readable(nilaiAwal, (set) => ...) fills in the value through set. The returned function is called when the last subscriber leaves — the place to clean up the interval. This is store integration with lifecycle: subscription and cleanup are handled automatically by Svelte.
derived computes a new value from one or more stores. Every time a source store changes, the derived value updates too:
import { writable, derived } from "svelte/store"
export const harga = writable(1000)
export const qty = writable(2)
export const total = derived([harga, qty], ([h, q]) => h * q)derived([harga, qty], ([h, q]) => h * q) accepts an array of stores as sources and a function to compute the result. total only updates when harga or qty changes — no wasted computation.
A custom store is any object that has a subscribe method. The most common pattern: wrap a writable and expose tailored methods. This hides internal details and prevents arbitrary modifications:
import { writable } from "svelte/store"
function buatKeranjang() {
const { subscribe, update } = writable([])
return {
subscribe,
tambah(item) {
update((items) => [...items, item])
},
hapus(id) {
update((items) => items.filter((i) => i.id !== id))
},
reset() {
update(() => [])
},
}
}
export const keranjang = buatKeranjang()buatKeranjang() returns an object with subscribe taken from the internal writable, plus the tambah, hapus, and reset methods. Components cannot touch the raw array directly — all changes go through methods. This is an example of the facade pattern that makes state management safe.
Outside components — for example in a module or function — you subscribe manually with .subscribe(callback), which returns an unsubscribe function:
import { count } from "../lib/stores.js"
const unsubscribe = count.subscribe((nilai) => {
console.log("Nilai baru:", nilai)
})
unsubscribe()count.subscribe((nilai) => ...) runs the callback every time the value changes, and once immediately at subscription time. The returned function is the unsubscribe — call it when you no longer need it, or you will leak memory.
Stores can be read with the $ prefix inside reactive statements and runes alike:
<script>
import { keranjang } from "../lib/keranjang.js"
</script>
<p>Total item: {$keranjang.length}</p>$keranjang.length is read reactively in the markup. When the cart changes, the text updates without writing any subscription code. Combining stores with $derived is also common for computing display values at the component level.
When combining stores with onMount and onDestroy, always make sure manual subscriptions are cleaned up.
The $ prefix in components automatically handles unsubscribe when the component is destroyed. But if you call .subscribe() manually, keep the unsubscribe function and call it in onDestroy — a habit that prevents memory leaks in large applications.
For state used by a single component, $state is the simplest choice. No subscription, no indirection:
<script>
let buka = $state(false)
</script>
<button onclick={() => (buka = !buka)}>
{buka ? "Tutup" : "Buka"} panel
</button>let buka = $state(false) is enough for state that never leaves the component. Adding a store to state like this only adds complexity without benefit.
Use stores when state is read or modified by many components that are not directly connected: user session, shopping cart, theme preferences, notifications. Stores also fit data fetched from a server and used across pages.
The principle is simple: start with $state, move up to a store when state starts being shared between many components. The following episodes use this pattern constantly, from auth to theme settings.
Key takeaways:
writable for mutable state, readable for fixed values, derived for derived values.$ prefix makes stores reactive in components; .subscribe() manually for non-component code.$ prefix.$state for local state; use stores when state is shared across components.In the next episode 7 we will discuss transitions and animations — the motion directives transition:, animate:, in:, and out:, plus custom animation with tweened and spring. Stores will serve as the source of values to animate. See you in the next episode!