Skip to the content.

Create a Redux-like store

A Redux store holds the application state and provides three methods: getState() to read the state, dispatch(action) to update it via a reducer, and subscribe(listener) to register change listeners.

Implement createStore(reducer, initialState) that returns an object with those three methods. The store must:

Workspace

Edit the module below and run the question's tests. Press Ctrl+Enter (or Cmd+Enter on a Mac) to run from the editor. Your draft is kept in this browser.

Solution

Approach 1: closure with mutable state

Keep the current state and a list of listeners in the closure. On creation, dispatch an init action to let the reducer set up the state.

export default function createStore(reducer, initialState) {
  let state = initialState;
  let listeners = [];

  // Initialize with @@INIT so reducers can set up state
  dispatch({ type: '@@INIT' });

  function getState() {
    return state;
  }

  function dispatch(action) {
    state = reducer(state, action);
    // Copy to avoid issues if a listener unsubscribes during iteration
    const copy = listeners.slice();
    for (let i = 0; i < copy.length; i++) {
      copy[i]();
    }
  }

  function subscribe(listener) {
    listeners.push(listener);
    return function unsubscribe() {
      const index = listeners.indexOf(listener);
      if (index !== -1) {
        listeners.splice(index, 1);
      }
    };
  }

  return { getState, dispatch, subscribe };
}

Approach 2: separate subscribe and unsubscribe APIs

Instead of returning an unsubscribe function, store subscriptions in a Map and return a token that can be used to unsubscribe later. This demonstrates an alternative API but behaves the same externally.

export default function createStore(reducer, initialState) {
  let state = initialState;
  const listeners = new Map();
  let nextToken = 0;

  // Init
  dispatch({ type: '@@INIT' });

  function getState() {
    return state;
  }

  function dispatch(action) {
    state = reducer(state, action);
    listeners.forEach(cb => cb());
  }

  function subscribe(listener) {
    const token = nextToken++;
    listeners.set(token, listener);
    return function unsubscribe() {
      listeners.delete(token);
    };
  }

  return { getState, dispatch, subscribe };
}

Trade-offs

  Closure with array Map‑based tokens
Simplicity Very straightforward; no extra data structures. Slightly more code but shows how to decouple subscription from listener identity.
Unsubscribing during iteration Requires copying the listener array to avoid skipping listeners. Safe to delete from a Map while iterating because we iterate over values only.
Memory overhead Array of listeners; duplicates possible if same listener added twice. Map stores token→listener; prevents duplicate listeners if same token reused (unlikely).
Typical use case Matches Redux’s internal implementation and is what most developers expect. Useful when you want to unsubscribe by a handle rather than passing the original listener back.

Both approaches satisfy the contract; choose the first for simplicity unless you need the token‑based unsubscribe pattern.