createSlice and createReducer run every case reducer inside Immer's produce. Your reducer receives a draft, a Proxy that wraps the current state, so you can write "mutating" code like state.todos.push(todo). Immer tracks each change to the draft and replays it as an immutable update, which creates a new state tree.
Structural sharing. Immer copies only the objects along the path you changed. Unchanged parts keep their old references, so components reading them don't re-render.
Mutate or return, not both. A reducer can change the draft or return a new value, but not both in one function. Assigning state = newValue does nothing, because it only rebinds the local variable.
Frozen results. Immer freezes the state it produces, so an accidental mutation outside a reducer throws an error.
To log a draft readably, wrap it in current(state). The mutating style only works inside Immer, so the same code in a plain reducer would really mutate state. The draft keeps the state's TypeScript type, so typing works as usual.
Rewriting in plainer words…
This answer doesn't lend itself to a diagram - it reads best . No credits were charged.