Volume 2

Q124: Immutability, Structural Sharing and State Updates

Difficulty: SeniorFrequency: HighAnswer time: 8-12 minutes

What Interviewers Want To Evaluate

This question checks whether you understand immutable update patterns beyond spreading objects. Interviewers want structural sharing, nested updates, arrays, normalized state, mutation hazards, Immer-style APIs, rendering optimization, and trade-offs.

Senior answers should explain why immutability helps UI systems and where it can become costly.

Short Interview Answer

Immutability means treating existing values as unchanged and creating new values for updates. Structural sharing means unchanged parts of a data structure are reused while changed paths get new references. This lets UI systems and memoization detect changes with cheap reference checks. In JavaScript, common immutable updates use object spread, array methods, normalization, and libraries like Immer. The goal is predictable updates, not blindly deep cloning everything.

Detailed Interview Answer

Mutation changes an existing value.

user.name = "Meera";

Immutable update creates a new value.

const nextUser = {
  ...user,
  name: "Meera",
};

The old value remains unchanged.

Why UI Systems Care

UI systems often rely on reference checks to know whether something changed.

previousUser !== nextUser;

If an object is mutated in place, the reference stays the same and change detection can miss the update.

Structural Sharing

Structural sharing reuses unchanged nested objects.

const nextState = {
  ...state,
  user: {
    ...state.user,
    name: "Meera",
  },
};

Only state and state.user are new. Other branches are reused.

Not Deep Clone

Structural sharing is different from deep cloning.

Deep cloning copies everything. Structural sharing copies only the path that changed.

This is usually better for performance and equality checks.

Array Updates

Immutable array patterns include:

const added = [...items, newItem];
const removed = items.filter((item) => item.id !== id);
const updated = items.map((item) =>
  item.id === id ? { ...item, done: true } : item,
);

Avoid mutating methods such as push, splice, and direct index assignment when updating immutable state.

Nested Update Problem

Deeply nested data makes immutable updates verbose.

const nextState = {
  ...state,
  projects: {
    ...state.projects,
    [projectId]: {
      ...state.projects[projectId],
      tasks: {
        ...state.projects[projectId].tasks,
        [taskId]: {
          ...state.projects[projectId].tasks[taskId],
          done: true,
        },
      },
    },
  },
};

This is a sign that state may need normalization or helper APIs.

Normalized State

Normalized state stores entities by ID.

const state = {
  tasksById: {
    t1: { id: "t1", title: "Read", done: false },
  },
  taskIds: ["t1"],
};

Updating one task requires copying only the task map and the task object.

Immer-Style Updates

Libraries like Immer let code appear mutable while producing immutable updates.

const nextState = produce(state, (draft) => {
  draft.user.name = "Meera";
});

This can improve ergonomics, but developers should still understand the immutability model underneath.

Object.freeze

Object.freeze can prevent mutation at runtime.

Object.freeze(user);

It is shallow unless nested objects are also frozen.

Freezing can catch mutation bugs in development but may be too expensive or restrictive everywhere.

Mutation Hazards

Mutation is dangerous when values are shared.

const defaultSettings = { theme: "light" };

function createUser(settings = defaultSettings) {
  settings.theme = "dark";
  return settings;
}

The shared default object is now changed for everyone.

Performance Trade-Offs

Immutability is not free. New objects allocate memory, and large updates can create pressure.

But mutation also has costs: harder debugging, missed renders, stale memoization, and accidental shared state.

Use structural sharing and normalized state to keep immutable updates efficient.

Common Mistakes

A common mistake is copying the top level but mutating nested data.

const next = { ...state };
next.user.name = "Meera";

This still mutates state.user.

Another mistake is deep cloning whole state trees for every update.

Senior Trade-Offs

Use immutability where change detection, history, undo, memoization, and predictable state matter.

Use controlled mutation inside isolated local algorithms when it is clearer and does not escape the function boundary.

The key is ownership and visibility.

Code or Design Example

function updateTask(state, taskId, patch) {
  return {
    ...state,
    tasksById: {
      ...state.tasksById,
      [taskId]: {
        ...state.tasksById[taskId],
        ...patch,
      },
    },
  };
}

This preserves structural sharing and creates new references only where data changed.

Debugging Workflow

When state updates behave strangely:

check accidental nested mutation
check array mutating methods
check stale memoized values
check reference equality before and after update
normalize deeply nested entities
use freeze or dev tools to catch mutation
avoid deep cloning entire state

Interview Framing

Explain structural sharing clearly. That is the bridge between immutable theory and real UI performance.

Revision Notes

Immutable updates create new references for changed paths and reuse unchanged branches. This supports cheap change detection.

Key Takeaways

Immutability is about predictable ownership and efficient change detection. Structural sharing is what makes it practical.

Follow-Up Questions

  1. What is immutability?
  2. What is structural sharing?
  3. Why does React care about references?
  4. How do you update arrays immutably?
  5. Why is top-level spread not enough for nested data?
  6. What is normalized state?
  7. How does Immer help?
  8. Is Object.freeze deep?
  9. When is mutation acceptable?
  10. Why is deep cloning usually not ideal?

How I Would Answer This In A Real Interview

I would say immutability creates new values instead of changing old ones, and structural sharing reuses unchanged branches. Then I would show a nested update, explain why shallow reference checks work, and mention normalization or Immer for complex state.