ConfigsApr 11, 2026·2 min read

Immer — Immutable State with Mutable Syntax

Immer lets you create the next immutable state by mutating the current one. Write mutating code as if it were normal JS and Immer produces a new immutable object via structural sharing. Built into Redux Toolkit and Zustand.

TL;DR
Immer lets you write mutable JavaScript code and automatically produces structurally-shared immutable state updates.
§01

What it is

Immer is a JavaScript library that simplifies immutable state management. Instead of writing spread operators and nested object copies, you write normal mutable code inside a produce function. Immer intercepts your mutations via a Proxy, applies structural sharing, and returns a new immutable object.

Immer is the state management engine behind Redux Toolkit. If you use createSlice or createReducer in Redux Toolkit, you are already using Immer. It targets any JavaScript or TypeScript project that needs immutable data patterns without the verbosity.

§02

How it saves time or tokens

Immutable updates in plain JavaScript are verbose and error-prone. Updating a nested property requires spreading every level of the object tree. Immer eliminates this boilerplate: you mutate the draft object directly, and Immer handles the rest. This reduces both code size and the chance of missing a spread operator.

For AI-generated code, Immer produces simpler, more readable state updates that are easier for LLMs to generate correctly.

§03

How to use

  1. Install Immer:
npm install immer
  1. Use the produce function:
import { produce } from 'immer';

const state = {
  users: [
    { id: 1, name: 'Alice', tags: ['admin'] },
    { id: 2, name: 'Bob', tags: ['user'] }
  ]
};

const nextState = produce(state, draft => {
  draft.users[0].tags.push('superadmin');
  draft.users.push({ id: 3, name: 'Charlie', tags: ['user'] });
});

// state is unchanged
// nextState has the updates with structural sharing
  1. In Redux Toolkit, Immer is built in:
const userSlice = createSlice({
  name: 'users',
  initialState: { list: [] },
  reducers: {
    addUser(state, action) {
      // This looks like a mutation but Immer handles it
      state.list.push(action.payload);
    }
  }
});
§04

Example

import { produce, enableMapSet } from 'immer';

// Enable Map and Set support
enableMapSet();

const state = new Map([['a', 1], ['b', 2]]);

const next = produce(state, draft => {
  draft.set('c', 3);
  draft.delete('a');
});
// state: Map { 'a' => 1, 'b' => 2 }
// next:  Map { 'b' => 2, 'c' => 3 }
§05

Related on TokRepo

§06

Common pitfalls

  • Do not return a value from the produce callback if you also mutate the draft. Either mutate the draft or return a new value, never both. Immer throws an error if you do both.
  • Map and Set support requires calling enableMapSet() before using produce with Maps or Sets. Without it, Immer ignores Map/Set mutations.
  • Immer uses Proxies, which have a small performance overhead. For hot paths processing thousands of updates per second, benchmark against manual immutable updates.

Frequently Asked Questions

Is Immer the same as Redux Toolkit?+

No. Immer is the immutability library that Redux Toolkit uses internally. You can use Immer standalone in any JavaScript project, with React useState, with MobX, or anywhere you need immutable state updates.

Does Immer work with TypeScript?+

Yes. Immer has full TypeScript support with correct type inference. The produce function preserves the type of your state object, and draft types are automatically mutable versions of your immutable types.

What is structural sharing?+

When Immer creates the next state, it only copies the objects that changed. Unchanged parts of the tree are shared by reference with the previous state. This is memory-efficient and enables fast equality checks via reference comparison.

Can Immer handle deeply nested objects?+

Yes. That is where Immer provides the most value. Instead of spreading five levels deep to update a nested property, you mutate the draft directly: `draft.level1.level2.level3.value = 'new'`.

Does Immer have performance overhead?+

Immer adds a small overhead due to Proxy creation. For typical UI state updates, the overhead is negligible. For bulk operations processing thousands of objects, consider using `produce` once for the entire batch rather than calling it per item.

Citations (3)

Discussion

Sign in to join the discussion.
No comments yet. Be the first to share your thoughts.

Related Assets