Build a File Tree / Folder Navigation Component

Updated · techinterview.org

File-tree navigation appears in IDEs (VS Code), file managers, document apps (Google Drive), and CMSes. The interview tests whether you understand recursive rendering, lazy loading, virtualization, and keyboard navigation patterns.

Functional requirements

  • Render tree of folders and files
  • Click folder → expand/collapse
  • Click file → select / open
  • Lazy-load children when expanding (for large trees)
  • Keyboard navigation
  • Drag-drop to reorganize
  • Context menu (rename, delete)

Data structure

Tree nodes:

{
  id: string,
  name: string,
  type: 'folder' | 'file',
  children?: Node[],
  isLoaded?: boolean // for lazy loading
}

Recursive rendering

Each node renders itself and (if folder + expanded) its children:

function TreeNode({ node, depth }) {
  const [expanded, setExpanded] = useState(false);
  return (
    <>
      <div onClick={() => setExpanded(!expanded)} style={{ paddingLeft: depth * 16 }}>
        {node.name}
      </div>
      {expanded && node.children?.map(c => (
        <TreeNode key={c.id} node={c} depth={depth + 1} />
      ))}
    </>
  );
}

Lazy loading

For trees with thousands of nodes, don’t load everything:

  • Folder shows expand chevron
  • Click expand → fetch children if not yet loaded
  • Show loading indicator
  • Cache loaded children so collapsing and re-expanding a folder reuses the data instead of refetching. Track this with the isLoaded flag and skip the network call when it’s already true.

Virtualization

For long trees (10K+ nodes visible), virtualize:

  • react-arborist: built specifically for trees. It handles flattening, virtualization, drag-drop, and keyboard nav out of the box, so you reach a working tree with far less code than rolling your own.
  • react-window with manual flattening. You convert the nested tree into a flat array of only the visible rows yourself, then let react-window render the window. More work, but full control over how each row renders.

Render only visible rows. Calculate item heights from depth + collapsed/expanded state.

Keyboard navigation

WAI-ARIA tree pattern:

  • Down: next visible item
  • Up: previous visible item
  • Right: expand if collapsed; first child if expanded
  • Left: collapse if expanded; parent if collapsed
  • Home: first item
  • End: last visible item
  • Enter: select / open

Selection

  • Single selection: click highlights. Keep one selected id in state; interviewers check that clicking a new row clears the old selection cleanly.
  • Multi-selection: Cmd+click toggles individual; Shift+click selects range. Range selection needs an anchor node — Shift+click selects every row between that anchor and the clicked row in visible (flattened) order, so you need the flat list to compute it.
  • Indicate selected with background color, and mirror it with aria-selected so the state is announced to screen readers, not only shown visually.

Drag and drop

Drag a file/folder onto another folder to move:

  • Use HTML5 DnD or a library (dnd-kit)
  • Drop indicator shows where the item will land
  • Validate: do not drop folder into its own descendant
  • Auto-expand folders on hover during drag

Context menu

Right-click on item → menu with actions (rename, delete, copy, paste).

  • Position menu near cursor; flip if near viewport edge
  • Keyboard accessible (context-menu key on Windows/Linux)
  • Close on click outside or Escape

Performance

  • Memoize node components. Wrap TreeNode in React.memo so a node only re-renders when its own props change, not every time a distant sibling expands.
  • Use stable IDs for keys. Key rows by the node id, never the array index, so React reuses DOM on reorder or insert instead of remounting rows and losing focus.
  • Avoid re-rendering entire tree on small changes. Keep expanded and selected state in a Set or map keyed by id rather than mutating the tree objects, so toggling one folder updates one node’s props.

Common mistakes

  • Re-rendering all nodes on every state change. Storing expansion state on the tree object forces a full re-render on each toggle; a tree that stutters at 1K nodes is a common red flag.
  • Loading entire tree upfront. Fetching every node blocks first paint and wastes bandwidth on folders the user never opens.
  • No keyboard support. Interviewers almost always probe arrow-key navigation and Enter-to-open; skipping it signals you overlooked accessibility.
  • Drag-drop allows invalid moves (folder into descendant). Without the descendant check you create a cycle and orphan the moved subtree.
  • Selection state not visually clear. Users lose track of what’s targeted right before a rename or delete, which turns into destructive mistakes.

ARIA

  • Wrapper: role="tree" — the single container that owns arrow-key focus for the whole widget.
  • Each node: role="treeitem" — one per visible row, folder or file.
  • aria-expanded for folders — set true or false so a screen reader announces open or closed; leave it off files, since a leaf has nothing to expand.
  • aria-selected for selected items — mirror your visual highlight so selection is spoken, not only colored.
  • aria-level for depth — the 1-based nesting depth, which lets assistive tech convey how deep a node sits.
  • aria-setsize and aria-posinset for siblings count — together they let a screen reader announce “item 3 of 7” within a folder.

Frequently Asked Questions

Should I use react-arborist or react-window?

react-arborist is built for trees specifically. react-window with manual flattening is more flexible but more code.

How do I handle a folder being renamed?

Inline edit: double-click name → input field. Save on blur or Enter. Update tree state and persist.

What about file icons?

Map file extension to icon. Library: vscode-icons, file-icons-js. Or build a small icon set.

newsletter

What's actually being asked right now

Interview patterns & comp trends, straight to your inbox.

No spam. Unsubscribe anytime.

newsletter

What's actually being asked right now

Interview patterns & comp trends, straight to your inbox.

No spam. Unsubscribe anytime.

1972 Soviet postage stamp commemorating the Mars 2 probe

worth a read

Mars For The Rest of Us — a weekly-or-more deep dive on the technical side of Mars exploration: rocket propulsion, microbiology, mission architecture, and everything in between. Written by Maciej Ceglowski.

Read it on Substack
Scroll to Top