An atom is the smallest unit of state: a token you create once and read or write through a store, while derived atoms compute their value from other atoms and stay in sync automatically. This is the model behind Jotai — atoms are stateless tokens, and a store holds every atom's actual value so the same atom can be shared across independent stores. You build a tiny version of it: atom to declare state, and createStore to read, write, and subscribe.
type Atom = object; // an opaque, stateless token — holds no value of its own
const miniJotaiAtoms: {
// atom(initialValue) → primitive atom
// atom(read) → derived atom, read = (get) => computedValue
// atom(read, write) → writable derived atom (optional)
atom(readOrInitial: unknown, write?: Function): Atom;
createStore(): {
get(atom: Atom): unknown; // current value (init on first read)
set(atom: Atom, value: unknown): void; // update a primitive / run a write fn
sub(atom: Atom, cb: () => void): () => void; // subscribe; returns unsubscribe
};
};
const { atom, createStore } = miniJotaiAtoms;
const count = atom(0); // primitive atom
const doubled = atom((get) => get(count) * 2); // derived atom
const store = createStore();
store.get(count); // 0
store.get(doubled); // 0
store.set(count, 5);
store.get(doubled); // 10 — recomputed from count
const count = atom(0);
const store = createStore();
const unsub = store.sub(count, () => console.log('changed'));
store.set(count, 1); // logs "changed"
store.set(count, 1); // same value — logs nothing
unsub();
store.set(count, 2); // logs nothing — unsubscribed
atom() returns an opaque token; every value lives in the store, so one atom used in two stores holds two independent values.get(otherAtom); the store records those reads and recomputes only when one of them changes, returning a cached value otherwise.Object.is, notifies nobody.We are building a tiny reactive store: atoms declare state, a store holds the values, and derived atoms recompute themselves only when something they read has changed.
You want to declare a piece of state once — say a count — and then declare values that depend on it, like doubled = count * 2, without wiring the updates by hand. When count changes, doubled should follow, and anything watching doubled should be told. The twist that makes this interesting: the atom itself must stay empty. It is just a name. All the real values live in a store, so the same count atom can be dropped into two different stores and keep two different values.
Think of an atom as a numbered locker tag, not the locker. The tag (atom(0)) is a stateless token you carry around. The store is the wall of lockers: it is what actually holds the value behind each tag, and a second store is a second wall with its own contents.
The quick version stores the value on the atom and recomputes derived atoms on every read.
function atom(read) {
if (typeof read === 'function') return { read };
return { value: read }; // the value lives ON the token
}
function createStore() {
const subscribers = new Map();
function get(a) {
if (typeof a.read === 'function') return a.read(get); // recompute every read
return a.value;
}
function set(a, value) {
a.value = value; // mutate the shared token
(subscribers.get(a) || []).forEach((cb) => cb());
}
function sub(a, cb) {
if (!subscribers.has(a)) subscribers.set(a, new Set());
subscribers.get(a).add(cb);
return () => subscribers.get(a).delete(cb);
}
return { get, set, sub };
}
This breaks in three ways. Because the value sits on the token, two stores share it — put count in Store A and Store B and they fight over one number. Because get reruns the read function every time, a derived atom is never cached. And because set only notifies subscribers of the atom you set, someone subscribed to doubled is never told when count changes — the store has no idea doubled even reads count.
Move all state into the store, record which atoms each derived read touches, and recompute lazily on read so a value is only ever computed when it is actually stale.
function atom(read, write) {
// A stateless token. It stores NOTHING about the current value — the store
// owns all state, so the same atom can be read from many stores at once.
if (typeof read === 'function') {
return write ? { read, write } : { read };
}
return { init: read };
}
function createStore() {
// atom token -> { value, epoch, deps }. `epoch` is a version counter bumped
// only when the value actually changes; `deps` (derived atoms only) maps each
// atom read during the last compute to the epoch it had at that moment.
const states = new Map();
// atom token -> Set<callback>
const listeners = new Map();
const isDerived = (a) => typeof a.read === 'function';
function readState(a) {
const prev = states.get(a);
if (!isDerived(a)) {
// Primitive: initialise from `init` on first read, then it just sits.
if (prev) return prev;
const fresh = { value: a.init, epoch: 0, deps: null };
states.set(a, fresh);
return fresh;
}
// Derived: reuse the cache unless a dependency changed since we computed.
if (prev && !isStale(prev)) return prev;
// Recompute, recording which atoms we read and the version each one had.
const deps = new Map();
const get = (dep) => {
const depState = readState(dep);
deps.set(dep, depState.epoch);
return depState.value;
};
const value = a.read(get);
const epoch = prev && Object.is(prev.value, value) ? prev.epoch : prev ? prev.epoch + 1 : 0;
const fresh = { value, epoch, deps };
states.set(a, fresh);
return fresh;
}
// Stale iff any recorded dependency now has a different epoch than when we
// read it. Reading a dependency here revalidates it first, so a shared node
// is recomputed at most once (no diamond glitch).
function isStale(state) {
for (const [dep, seenEpoch] of state.deps) {
if (readState(dep).epoch !== seenEpoch) return true;
}
return false;
}
function get(a) {
return readState(a).value;
}
function set(a, value) {
if (isDerived(a)) {
if (typeof a.write === 'function') return a.write(get, set, value);
throw new Error('Cannot set a read-only derived atom');
}
const prev = readState(a);
if (Object.is(prev.value, value)) return; // no change — nobody to notify
// Snapshot the version of every subscribed atom BEFORE the change.
const before = new Map();
for (const subAtom of listeners.keys()) before.set(subAtom, readState(subAtom).epoch);
states.set(a, { value, epoch: prev.epoch + 1, deps: null });
// Revalidate every subscribed atom; notify those whose version moved.
const changed = [];
for (const subAtom of listeners.keys()) {
if (readState(subAtom).epoch !== before.get(subAtom)) changed.push(subAtom);
}
for (const subAtom of changed) {
for (const cb of [...listeners.get(subAtom)]) cb();
}
}
function sub(a, cb) {
readState(a); // establish its value + deps so a later set can diff it
let subs = listeners.get(a);
if (!subs) {
subs = new Set();
listeners.set(a, subs);
}
subs.add(cb);
return () => {
const current = listeners.get(a);
if (!current) return;
current.delete(cb);
if (current.size === 0) listeners.delete(a);
};
}
return { get, set, sub };
}
const miniJotaiAtoms = { atom, createStore };
module.exports = { miniJotaiAtoms };
Three ideas replace the naive version. State lives in the store's states map, keyed by the atom token, so two stores never collide. Each derived atom records its dependencies as atom -> version pairs while it runs, so isStale can ask "did anything I read actually change?" instead of recomputing blindly. And an epoch counter that only advances when the value truly changes lets set tell exactly which subscribers to notify — and lets a diamond settle without recomputing the join twice.
Start with count = atom(0) and doubled = atom((get) => get(count) * 2). Subscribe to doubled, then run store.set(count, 5).
sub(doubled, cb) reads doubled once. That computes doubled: it reads count (initialised to 0, epoch 0), records deps = { count: 0 }, and stores doubled as value 0, epoch 0.set(count, 5) sees the old value is 0 and 5 is different, so it does not bail. It snapshots the subscribed atoms first: doubled is at epoch 0, so before = { doubled: 0 }.count as value 5, epoch 1.doubled finds it stale — its recorded count: 0 no longer matches count's epoch 1 — so it recomputes to 10 and bumps to epoch 1.doubled moved from epoch 0 to 1, so it is in the changed list, and its callback fires once. A later store.get(doubled) returns the cached 10 without recomputing.a.value = v mutates a shared token, so two stores overwrite each other. Fix: keep a Map in the store from atom to its state.Object.is(prev, next).get handed to a read function does not record the read, that value can never be invalidated. Fix: the tracking get records every atom it returns.set calls into one notification pass so subscribers run once per batch, not once per set.set(count, (prev) => prev + 1) for updates that read the current value.Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.
An atom is the smallest unit of state: a token you create once and read or write through a store, while derived atoms compute their value from other atoms and stay in sync automatically. This is the model behind Jotai — atoms are stateless tokens, and a store holds every atom's actual value so the same atom can be shared across independent stores. You build a tiny version of it: atom to declare state, and createStore to read, write, and subscribe.
type Atom = object; // an opaque, stateless token — holds no value of its own
const miniJotaiAtoms: {
// atom(initialValue) → primitive atom
// atom(read) → derived atom, read = (get) => computedValue
// atom(read, write) → writable derived atom (optional)
atom(readOrInitial: unknown, write?: Function): Atom;
createStore(): {
get(atom: Atom): unknown; // current value (init on first read)
set(atom: Atom, value: unknown): void; // update a primitive / run a write fn
sub(atom: Atom, cb: () => void): () => void; // subscribe; returns unsubscribe
};
};
const { atom, createStore } = miniJotaiAtoms;
const count = atom(0); // primitive atom
const doubled = atom((get) => get(count) * 2); // derived atom
const store = createStore();
store.get(count); // 0
store.get(doubled); // 0
store.set(count, 5);
store.get(doubled); // 10 — recomputed from count
const count = atom(0);
const store = createStore();
const unsub = store.sub(count, () => console.log('changed'));
store.set(count, 1); // logs "changed"
store.set(count, 1); // same value — logs nothing
unsub();
store.set(count, 2); // logs nothing — unsubscribed
atom() returns an opaque token; every value lives in the store, so one atom used in two stores holds two independent values.get(otherAtom); the store records those reads and recomputes only when one of them changes, returning a cached value otherwise.Object.is, notifies nobody.We are building a tiny reactive store: atoms declare state, a store holds the values, and derived atoms recompute themselves only when something they read has changed.
You want to declare a piece of state once — say a count — and then declare values that depend on it, like doubled = count * 2, without wiring the updates by hand. When count changes, doubled should follow, and anything watching doubled should be told. The twist that makes this interesting: the atom itself must stay empty. It is just a name. All the real values live in a store, so the same count atom can be dropped into two different stores and keep two different values.
Think of an atom as a numbered locker tag, not the locker. The tag (atom(0)) is a stateless token you carry around. The store is the wall of lockers: it is what actually holds the value behind each tag, and a second store is a second wall with its own contents.
The quick version stores the value on the atom and recomputes derived atoms on every read.
function atom(read) {
if (typeof read === 'function') return { read };
return { value: read }; // the value lives ON the token
}
function createStore() {
const subscribers = new Map();
function get(a) {
if (typeof a.read === 'function') return a.read(get); // recompute every read
return a.value;
}
function set(a, value) {
a.value = value; // mutate the shared token
(subscribers.get(a) || []).forEach((cb) => cb());
}
function sub(a, cb) {
if (!subscribers.has(a)) subscribers.set(a, new Set());
subscribers.get(a).add(cb);
return () => subscribers.get(a).delete(cb);
}
return { get, set, sub };
}
This breaks in three ways. Because the value sits on the token, two stores share it — put count in Store A and Store B and they fight over one number. Because get reruns the read function every time, a derived atom is never cached. And because set only notifies subscribers of the atom you set, someone subscribed to doubled is never told when count changes — the store has no idea doubled even reads count.
Move all state into the store, record which atoms each derived read touches, and recompute lazily on read so a value is only ever computed when it is actually stale.
function atom(read, write) {
// A stateless token. It stores NOTHING about the current value — the store
// owns all state, so the same atom can be read from many stores at once.
if (typeof read === 'function') {
return write ? { read, write } : { read };
}
return { init: read };
}
function createStore() {
// atom token -> { value, epoch, deps }. `epoch` is a version counter bumped
// only when the value actually changes; `deps` (derived atoms only) maps each
// atom read during the last compute to the epoch it had at that moment.
const states = new Map();
// atom token -> Set<callback>
const listeners = new Map();
const isDerived = (a) => typeof a.read === 'function';
function readState(a) {
const prev = states.get(a);
if (!isDerived(a)) {
// Primitive: initialise from `init` on first read, then it just sits.
if (prev) return prev;
const fresh = { value: a.init, epoch: 0, deps: null };
states.set(a, fresh);
return fresh;
}
// Derived: reuse the cache unless a dependency changed since we computed.
if (prev && !isStale(prev)) return prev;
// Recompute, recording which atoms we read and the version each one had.
const deps = new Map();
const get = (dep) => {
const depState = readState(dep);
deps.set(dep, depState.epoch);
return depState.value;
};
const value = a.read(get);
const epoch = prev && Object.is(prev.value, value) ? prev.epoch : prev ? prev.epoch + 1 : 0;
const fresh = { value, epoch, deps };
states.set(a, fresh);
return fresh;
}
// Stale iff any recorded dependency now has a different epoch than when we
// read it. Reading a dependency here revalidates it first, so a shared node
// is recomputed at most once (no diamond glitch).
function isStale(state) {
for (const [dep, seenEpoch] of state.deps) {
if (readState(dep).epoch !== seenEpoch) return true;
}
return false;
}
function get(a) {
return readState(a).value;
}
function set(a, value) {
if (isDerived(a)) {
if (typeof a.write === 'function') return a.write(get, set, value);
throw new Error('Cannot set a read-only derived atom');
}
const prev = readState(a);
if (Object.is(prev.value, value)) return; // no change — nobody to notify
// Snapshot the version of every subscribed atom BEFORE the change.
const before = new Map();
for (const subAtom of listeners.keys()) before.set(subAtom, readState(subAtom).epoch);
states.set(a, { value, epoch: prev.epoch + 1, deps: null });
// Revalidate every subscribed atom; notify those whose version moved.
const changed = [];
for (const subAtom of listeners.keys()) {
if (readState(subAtom).epoch !== before.get(subAtom)) changed.push(subAtom);
}
for (const subAtom of changed) {
for (const cb of [...listeners.get(subAtom)]) cb();
}
}
function sub(a, cb) {
readState(a); // establish its value + deps so a later set can diff it
let subs = listeners.get(a);
if (!subs) {
subs = new Set();
listeners.set(a, subs);
}
subs.add(cb);
return () => {
const current = listeners.get(a);
if (!current) return;
current.delete(cb);
if (current.size === 0) listeners.delete(a);
};
}
return { get, set, sub };
}
const miniJotaiAtoms = { atom, createStore };
module.exports = { miniJotaiAtoms };
Three ideas replace the naive version. State lives in the store's states map, keyed by the atom token, so two stores never collide. Each derived atom records its dependencies as atom -> version pairs while it runs, so isStale can ask "did anything I read actually change?" instead of recomputing blindly. And an epoch counter that only advances when the value truly changes lets set tell exactly which subscribers to notify — and lets a diamond settle without recomputing the join twice.
Start with count = atom(0) and doubled = atom((get) => get(count) * 2). Subscribe to doubled, then run store.set(count, 5).
sub(doubled, cb) reads doubled once. That computes doubled: it reads count (initialised to 0, epoch 0), records deps = { count: 0 }, and stores doubled as value 0, epoch 0.set(count, 5) sees the old value is 0 and 5 is different, so it does not bail. It snapshots the subscribed atoms first: doubled is at epoch 0, so before = { doubled: 0 }.count as value 5, epoch 1.doubled finds it stale — its recorded count: 0 no longer matches count's epoch 1 — so it recomputes to 10 and bumps to epoch 1.doubled moved from epoch 0 to 1, so it is in the changed list, and its callback fires once. A later store.get(doubled) returns the cached 10 without recomputing.a.value = v mutates a shared token, so two stores overwrite each other. Fix: keep a Map in the store from atom to its state.Object.is(prev, next).get handed to a read function does not record the read, that value can never be invalidated. Fix: the tracking get records every atom it returns.set calls into one notification pass so subscribers run once per batch, not once per set.set(count, (prev) => prev + 1) for updates that read the current value.Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.