Deep Cloning in Redux State
A recent frontend performance issue surfaced: interactions became sluggish and eventually blew up memory. The culprit was, unsurprisingly, us—and the weapon of choice was
cloneDeep.

Consider this reducer:
const updateDetailReducer = (state: IDetailState, action) => {
const detail = _.cloneDeep(state);
return { ...detail, ...action.params };
};
Does it work? Functionally, yes—the state updates as expected.
But it’s terrible for performance because of the deep clone. Every property inside detail becomes a brand-new object. If dozens of connected components read detail or its children, the page crawls. The more interactions, the worse it gets. Remember: when component props change, React re-renders. Most of those props didn’t change, yet deep cloning forces every related component to re-render, wasting cycles and killing performance.
shouldComponentUpdate
Deep cloning triggers a storm of re-renders because React’s shouldComponentUpdate performs a shallow comparison (basic types by value, objects by reference). Fresh references everywhere mean React thinks everything changed.
Lodash cloneDeep
Since we used Lodash’s cloneDeep, let’s contrast it with clone and the native JSON.parse(JSON.stringify()). I’ve hit errors deep cloning with JSON.parse(JSON.stringify()) that cloneDeep handled, so they definitely behave differently.
cloneDeep
Iterates recursively:
const a = {info: {name: 'xxx'}};
const b = _.cloneDeep(a);
console.log(a === b); // false
console.log(a.info === b.info); // true
console.log(a.info.name === b.info.name); // true
clone
Non-iterative copy:
const a = {info: {name: 'xxx'}};
const b = _.clone(a);
console.log(a === b); // false
console.log(a.info === b.info); // true
JSON.parse(JSON.stringify())
Similar to cloneDeep, but only works with numbers, strings, and plain objects without functions or Symbols.
So for pure deep clone behavior, cloneDeep is more complete and safer.
const a = {info: {name: 'xxx'}, formatter: () => this.info.name + '@'};
const b = JSON.parse(JSON.stringify(a));
console.log(typeof a.formatter); // function
console.log(typeof b.formatter); // undefined
Final Thoughts
Fixing the bug was quick, but the lesson sticks.
- Redux uses a single state tree. Each reducer updates part of that tree, but that doesn’t mean you should clone the entire branch.
- Deep clones aren’t forbidden in reducers—but they’re rarely necessary. The higher up you clone, the more components you invalidate. Use them sparingly.

