What does Immer.js do inside Redux Toolkit?
Short answer
Immer.js inside Redux Toolkit is responsible for immutable state updates. It lets you write code as if you were mutating an object, but it actually creates a new, unchanged state using a Proxy.
What happens when you call createSlice()
When you write a reducer in RTK like this:
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment(state) {
state.value++ // ← you seem to be mutating
},
},
})RTK does not store this reducer directly. Under the hood it wraps your function with Immer:
import { produce } from 'immer'
function wrappedReducer(state, action) {
return produce(state, draft => {
state.value++ // ← becomes "draft.value++"
})
}Immer takes state, creates a "draft" of it,
lets you make changes to it, and once finished returns a new state,
in which only the needed fields are changed, while everything else stays the same (by reference).
How produce() works (the heart of Immer)
Here is a minimal implementation of the "magic":
import { produce } from 'immer'
const baseState = { count: 1, user: { name: 'Maria' } }
const nextState = produce(baseState, draft => {
draft.count++
draft.user.name = 'Alex'
})
console.log(nextState)
// { count: 2, user: { name: 'Alex' } }
console.log(baseState)
// { count: 1, user: { name: 'Maria' } } unchangedWhat Immer actually does under the hood
- Creates a Proxy for the
stateobject Every property access (get,set,delete) is intercepted. - Remembers every change you make Immer does not change the object right away - it records instructions:
- increment count
- change user.name- After exiting
produce()Immer creates a new state tree, copying only the changed parts. Everything that was not changed stays at the old references → this is very memory-efficient. - Returns the new state The new object is "clean" and immutable - it can safely be passed on into Redux.
Example: nested changes
const state = {
user: { name: 'Maria', age: 25 },
settings: { theme: 'dark' }
}
const next = produce(state, draft => {
draft.user.age = 26
})Immer:
- Creates a new copy of only the
userbranch settingsstays at the same reference- So
next.settings === state.settings→true
This makes updates very performant, because only the changed part of the tree gets copied.
How RTK uses Immer internally
Redux Toolkit integrates Immer into createReducer() and createSlice().
Every reducer you declare effectively becomes:
(state, action) => produce(state, draft => {
// your code
})In RTK this happens automatically, so you do not need to call produce() explicitly.
Example without RTK (plain Redux)
function userReducer(state = { name: 'Maria', age: 25 }, action) {
switch (action.type) {
case 'UPDATE_NAME':
return { ...state, name: action.payload }
default:
return state
}
}Example with RTK (Immer inside)
const userSlice = createSlice({
name: 'user',
initialState: { name: 'Maria', age: 25 },
reducers: {
updateName(state, action) {
state.name = action.payload // works thanks to Immer
}
}
})Both do the same thing - only in RTK the code is twice as short, and immutability is guaranteed automatically.
Why this matters
| Problem without Immer | What Immer solves |
|---|---|
You have to copy objects manually ({ ...state }) | Does it automatically |
| Mutation bugs | Eliminates them |
| Hard to update nested structures | You can access it directly (state.user.name = ...) |
| Slow deep copying | Copies only the changed branches |
| A lot of boilerplate | The code becomes natural and short |
Visually (what Immer does)
[original state]
│
▼
a proxy (draft) is created
│
▼
you "mutate" the draft
│
▼
Immer records the changes
│
▼
returns the new state (immutable)Summary
| What Immer does inside RTK | What it looks like |
|---|---|
| Creates a state "draft" via a Proxy | draft |
Lets you mutate the draft | state.value++ |
| Tracks all changes | through Proxy traps |
| Creates a new state on exit | an immutable copy |
| Copies only the changed branches | efficient and fast |
| Guarantees state safety | without manual spreads |
Summary definition:
Immer.js in Redux Toolkit is an "invisible intermediary" that turns your "mutating" code into a clean, immutable state transformation with minimal memory changes and maximum safety.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.