Back

Recursive File Explorer

medium

Streak

0 days

Progress

0%

Submitted

0

Recursive File Explorer

React35 minmediumFreeNew

Prompt

A File Explorer is a recursive tree navigation component — arguably the most common use case for recursive React components in frontend interviews. Your task is to build an interactive file system tree where folders can be expanded and collapsed by clicking, files are displayed as leaves, and the entire tree is rendered from a nested data structure without knowing its depth in advance. The recursive component pattern is the core of this question. A FileNode component renders itself, and if the node is a folder, it also renders a list of child FileNode components. This is identical to how VS Code's file explorer, GitHub's repository browser, and every IDE's project tree works. Getting the toggle logic right (each folder independently tracks its own open/closed state) and handling the rendering correctly for arbitrary nesting depth is what the question tests.

Requirements

  • →Render a nested file system tree from a hierarchical data object
  • →Folders can be expanded (show children) and collapsed (hide children) by clicking
  • →Files are leaf nodes — clicking them has no expand/collapse behavior
  • →Each folder/file shows an appropriate icon (open folder, closed folder, file)
  • →Root folder(s) expanded by default
  • →Correct visual indentation by nesting level
  • →No fixed depth limit — works for arbitrary nesting
Example
Loading preview...
For the best coding experience, we recommend using a desktop device.
Preparing Sandbox...
Premium interview report

What interviewers score in this build

Use this before reading the code. It tells you what to say, what to test, and where machine-coding candidates usually lose points.

Interview signals

  • React: FileNode is a recursive component. useState(isOpen) inside each FileNode — independent per node. Children rendered only when isOpen is true. Root nodes initialized with isOpen=true.
  • HTML/CSS: Indentation via paddingLeft = depth * 16px inline style. Folder icon changes between open (📂) and closed (📁). File icon is fixed (📄). Hover highlight on each row. Font-family monospace for file names.
  • Component Architecture: Single FileNode component handles both files and folders via node.type === 'folder' check. No separate FolderNode and FileNode — one component handles both. FILE_SYSTEM constant defined outside component.
  • State Management: Each folder node has its own independent isOpen state. No global tree state. This is correct because folder open/close state doesn't need to be shared across the tree.

Time checkpoints

  1. 1

    0:00: Read prompt. Key insight: recursive component, local isOpen state per node. Define FILE_SYSTEM data structure.

  2. 2

    4:00: Build FileNode component. Render node label and icon. Handle isOpen toggle on folder click.

  3. 3

    10:00: Add recursive render: when isOpen, map over node.children and render FileNode for each.

  4. 4

    15:00: Add depth prop. Wire paddingLeft indentation. Verify deep nesting renders correctly.

Edge-case checklist

Empty data and first-load state
Slow network, failed request, and retry path
Keyboard navigation and focus movement
Large input size, re-render pressure, and cleanup

Common mistakes

  • Starting with JSX before naming state and events.
  • Ignoring accessibility until the final minute.
  • Over-building abstractions instead of finishing the required behavior.
  • Failing to narrate trade-offs while coding.
SolutionRead-only · Live Preview

Technical Explanation

Problem Understanding

The key insight: you cannot write this without recursion. You don't know how deep the tree is. A folder can contain folders that contain more folders. The only way to handle this is a component that renders itself for its children — a recursive component.

Why Local State — Not Lifted State

This is an important design question the interviewer might ask directly. Why put isOpen inside each FileNode instead of tracking all folder states at the root level?

// ❌ Lifted approach — complex, fragile
const [openFolders, setOpenFolders] = useState(new Set(['root', 'src']));
// Problem: requires a unique ID for every node in the tree.
// Managing a global Set of open folder IDs is more complex
// and adds no benefit when folder open/close doesn't need
// to be shared with any other component.
// ✅ Local state — simple, correct
function FileNode({ node, depth = 0 }) {
  const [isOpen, setIsOpen] = useState(depth === 0); // root starts open
  // Each node independently manages its own open state — this is fine
  // because no other component needs to know which folders are open.
}
💡 Interview Tip: Say this when asked: "Folder open/close state is purely local — no other component needs to know which folders are expanded. Local state inside the recursive component is simpler and equally correct. I'd lift it only if, for example, a parent needed to show 'X folders expanded' or serialize the open state to a URL."

The Recursive Component

function FileNode({ node, depth = 0 }) {
  const [isOpen, setIsOpen] = useState(depth === 0);
  const isFolder = node.type === 'folder';

  function getIcon() {
    if (!isFolder) return '📄';
    return isOpen ? '📂' : '📁';
  }

  return (
    <div>
      {/* This node's row */}
      <div
        style={{ paddingLeft: 8 + depth * 18 }}
        className={`node-row ${isFolder ? 'node-folder' : 'node-file'}`}
        onClick={() => isFolder && setIsOpen(o => !o)}
      >
        {isFolder && (
          <span className={`arrow ${isOpen ? 'arrow-open' : ''}`}>▸</span>
        )}
        <span className="icon">{getIcon()}</span>
        <span className="name">{node.name}</span>
      </div>

      {/* Children — only rendered when folder is open */}
      {isFolder && isOpen && node.children?.map((child, i) => (
        <FileNode
          key={`${child.name}-${i}`}
          node={child}
          depth={depth + 1} // depth increments on each recursive call
        />
      ))}
    </div>
  );
}

The key Prop on Recursive Children

Using key={child.name + '-' + i} (not just child.name) handles the case where two siblings have the same name — unlikely in a real filesystem but possible in interview data. Adding the index guarantees uniqueness within siblings.

Conditional Render vs CSS Display

// ✅ Conditional render — children are removed from DOM when collapsed
{isFolder && isOpen && node.children?.map(...)}

// ❌ display:none — children are in the DOM but hidden
<div style={{ display: isOpen ? 'block' : 'none' }}>
  {node.children?.map(...)}
</div>

For a file tree with thousands of nodes, conditional rendering is better — it keeps the DOM lean. Each collapsed folder's children don't exist in the DOM at all, not just hidden.

Common Pitfalls Summary

  • Trying to write it without recursion: A fixed-depth approach (hardcoding 3 levels) breaks immediately if the tree is deeper.
  • Lifting all folder states to root: Unnecessarily complex. Local state per node is simpler and correct when no external sharing is needed.
  • Using display:none instead of conditional render: For large trees, this keeps all nodes in the DOM — defeats the purpose of collapsing.
  • Missing depth prop increment: Without depth + 1 on each recursive call, all nodes render at the same indentation level.
  • onClick on file nodes: Only folders should respond to click. Guard with isFolder check.

Interview Criteria

React

FileNode is a recursive component. useState(isOpen) inside each FileNode — independent per node. Children rendered only when isOpen is true. Root nodes initialized with isOpen=true.

HTML/CSS

Indentation via paddingLeft = depth * 16px inline style. Folder icon changes between open (📂) and closed (📁). File icon is fixed (📄). Hover highlight on each row. Font-family monospace for file names.

Component Architecture

Single FileNode component handles both files and folders via node.type === 'folder' check. No separate FolderNode and FileNode — one component handles both. FILE_SYSTEM constant defined outside component.

State Management

Each folder node has its own independent isOpen state. No global tree state. This is correct because folder open/close state doesn't need to be shared across the tree.

Edge Cases

Empty folder (children: []) shows as a folder but has nothing to expand. Files with no children never call useState-based toggle. Root node starts open. Very deep nesting renders correctly.

Time Checkpoints

0:00

0:00: Read prompt. Key insight: recursive component, local isOpen state per node. Define FILE_SYSTEM data structure.

4:00

4:00: Build FileNode component. Render node label and icon. Handle isOpen toggle on folder click.

10:00

10:00: Add recursive render: when isOpen, map over node.children and render FileNode for each.

15:00

15:00: Add depth prop. Wire paddingLeft indentation. Verify deep nesting renders correctly.

20:00

20:00: Differentiate file vs folder icons. Set root to isOpen=true by default.

25:00

25:00: Polish hover states, expand arrow, empty folder handling.

Streak

0 days

Last active: Sign in to track

Progress

0%

0/0 solved

Submitted

0

Solutions pushed to review history.