Blogs / Machine Coding Notes

Frontend Machine Coding: Nested Checkbox Tree in React

Learn how to solve the Nested Checkbox Tree machine coding problem in React using recursion, parent-child synchronization, and scalable state management.

Apr 16, 20268 min readSevyDevy Team
ReactMachine CodingFrontend InterviewJavaScriptRecursionState ManagementCheckbox Tree

Table of content

  1. 1. What is the Nested Checkbox Tree Problem?
  2. 2. Problem Statement
  3. 3. Functional Requirements
  4. 4. Why Interviewers Ask This
  5. 5. Real-World Use Cases
  6. 6. The Correct Mental Model
  7. 7. Recommended State Strategy
  8. 8. Core Operations You Need
  9. 9. Getting All Descendants
  10. 10. Building a Parent Map
  11. 11. Handling Parent to Child Updates
  12. 12. Handling Child to Parent Updates
  13. 13. Recursive Rendering
  14. 14. Edge Cases to Think About
  15. 15. Common Mistakes Candidates Make
  16. 16. Bonus Improvements
  17. 17. What Interviewers Usually Evaluate
  18. 18. Final Takeaway

What is the Nested Checkbox Tree Problem?

The Nested Checkbox Tree is a frontend machine coding problem where you build a hierarchical tree of checkboxes. Each node can contain child nodes, and the checked state must stay synchronized between parent and children. This problem is commonly used in React interviews to test recursion, state management, and UI logic.

The challenge is not in rendering checkboxes. The real challenge is designing correct state propagation in both directions. When a parent is checked, all descendants must be checked. When a child changes, all ancestors may need to be recalculated.

Problem Statement

Build a Nested Checkbox Component in React that represents a tree-like hierarchical structure of items. Each node should display a checkbox and label. Nodes may contain child nodes recursively. Your task is to implement correct synchronization between parent and child checkbox states.

const CheckboxesData = [
  {
    id: 1,
    label: "Fruits",
    children: [
      { id: 2, label: "Apple" },
      { id: 3, label: "Banana" },
      {
        id: 4,
        label: "Citrus",
        children: [
          { id: 5, label: "Orange" },
          { id: 6, label: "Lemon" },
        ],
      },
    ],
  },
  {
    id: 7,
    label: "Vegetables",
    children: [
      { id: 8, label: "Carrot" },
      { id: 9, label: "Broccoli" },
    ],
  },
];

Functional Requirements

  • When a parent checkbox is checked, all children and nested children must also be checked.
  • When a parent checkbox is unchecked, all children and nested children must also be unchecked.
  • If all children of a parent are checked, the parent should become checked.
  • If any child is unchecked, the parent should become unchecked.
  • The solution must support infinite nesting levels.
  • The implementation should not be hardcoded for a fixed tree depth.

Why Interviewers Ask This

This question tests much more than basic React rendering. It checks whether you can think recursively, manage state immutably, and design UI logic that scales. A strong solution shows that you understand tree structures, derived state, and component design patterns used in real products.

Real-World Use Cases

  • Permission and role management systems
  • Folder and file explorers
  • Nested filters in e-commerce applications
  • Admin dashboards with grouped settings
  • Feature toggle configuration panels

The Correct Mental Model

The easiest way to solve this problem is to think in two directions. First is downward propagation, where a parent affects all descendants. Second is upward recalculation, where children affect the checked state of their ancestors. If your solution handles only one direction, the logic will fail in real usage.

  • Downward flow: parent updates all children
  • Upward flow: child updates all parents

A scalable solution should avoid mutating the original tree. Instead, keep the checked state in a flat map where each node ID points to a boolean value. This allows constant-time lookups and makes updates easier to reason about.

const [checkedMap, setCheckedMap] = useState<Record<number, boolean>>({});

This structure is far easier to manage than embedding checked values directly inside the nested data. It also makes it simpler to derive parent states and support deeply nested trees.

Core Operations You Need

  • Find all descendants of a node so parent updates can flow downward.
  • Build a child-to-parent map so updates can travel upward efficiently.
  • Recalculate parent state after any child change.
  • Render the tree recursively so the same logic works for any depth.

Getting All Descendants

When a parent checkbox changes, every nested child should receive the same checked value. A recursive helper function can collect all descendant IDs for that node.

const getAllChildren = (node: any): number[] => {
  let children: number[] = [];

  if (node.children) {
    for (const child of node.children) {
      children.push(child.id);
      children = [...children, ...getAllChildren(child)];
    }
  }

  return children;
};

Building a Parent Map

To update ancestors efficiently, build a map where each child node stores a reference to its parent ID. This avoids expensive tree traversal every time a checkbox changes.

const buildParentMap = (
  nodes: any[],
  parentId: number | null = null,
  map: Record<number, number> = {}
) => {
  for (const node of nodes) {
    if (parentId !== null) {
      map[node.id] = parentId;
    }

    if (node.children) {
      buildParentMap(node.children, node.id, map);
    }
  }

  return map;
};

Handling Parent to Child Updates

Once a checkbox is toggled, update the current node and all its descendants in one immutable state update. This covers the downward flow of the problem.

const handleCheck = (node: any, isChecked: boolean) => {
  const updated = { ...checkedMap };

  updated[node.id] = isChecked;

  const children = getAllChildren(node);
  for (const childId of children) {
    updated[childId] = isChecked;
  }

  updateParentState(node.id, updated);
  setCheckedMap(updated);
};

Handling Child to Parent Updates

The difficult part of this problem is upward propagation. After a child changes, you must walk up the tree and check whether all sibling nodes are selected. If every sibling is checked, the parent becomes checked. Otherwise, it becomes unchecked.

const updateParentState = (
  nodeId: number,
  state: Record<number, boolean>,
  parentMap: Record<number, number>,
  childrenMap: Record<number, number[]>
) => {
  const parentId = parentMap[nodeId];
  if (!parentId) return;

  const siblingIds = childrenMap[parentId] || [];
  const allChecked = siblingIds.every((id) => state[id]);

  state[parentId] = allChecked;

  updateParentState(parentId, state, parentMap, childrenMap);
};

Recursive Rendering

Since the tree can have any depth, recursive rendering is the most natural approach. Each node component renders itself and then renders its children using the same component.

const TreeNode = ({ node }: { node: any }) => {
  return (
    <div style={{ marginLeft: 20 }}>
      <label>
        <input
          type="checkbox"
          checked={!!checkedMap[node.id]}
          onChange={(e) => handleCheck(node, e.target.checked)}
        />
        {node.label}
      </label>

      {node.children?.map((child: any) => (
        <TreeNode key={child.id} node={child} />
      ))}
    </div>
  );
};

Edge Cases to Think About

  • Deeply nested trees
  • Parents with only one child
  • All nodes unchecked initially
  • Mixed checked and unchecked branches
  • Very large trees with thousands of nodes

Common Mistakes Candidates Make

  • They only implement parent-to-child synchronization.
  • They forget to update parent state after child changes.
  • They mutate the original nested tree directly.
  • They hardcode logic for only one or two levels of nesting.
  • They ignore scalability and unnecessary re-renders.

Bonus Improvements

A basic solution can pass many interview rounds, but strong candidates often mention or implement enhancements. The most common bonus improvement is the indeterminate state, where a parent appears partially selected when some but not all children are checked.

  • Add indeterminate state for partial selection
  • Use memoization to reduce re-renders
  • Support keyboard accessibility
  • Use virtualization for very large trees
  • Convert the component into a controlled component

What Interviewers Usually Evaluate

  • Can you think recursively without hardcoding levels?
  • Can you design correct state synchronization?
  • Can you update state immutably and cleanly?
  • Can you write modular and readable React code?
  • Can you discuss scaling and follow-up improvements?

Final Takeaway

The Nested Checkbox Tree is a classic frontend machine coding question because it combines recursion, tree traversal, state synchronization, and React architecture into one problem. If you solve it cleanly and explain the downward and upward propagation model clearly, you immediately stand out as a stronger frontend engineer in interviews.

Related blogs