Build Breadcrumb Navigation
Learn how to build a file-explorer style breadcrumb navigation from scratch. Navigate into nested folders, click breadcrumb segments to jump back, and render folder contents dynamically. A clean interview question that tests tree data structures, state management, and UI patterns.
Table of Contents
Problem Statement
Build a file explorer with breadcrumb navigation. Users can browse a nested folder structure, clicking folders to navigate deeper and using the breadcrumb trail to jump back to any ancestor folder.
- Display the current folder's contents (subfolders and files)
- Clicking a folder navigates into it
- Show a breadcrumb trail: Home / Documents / Work
- Clicking any breadcrumb segment navigates back to that folder
- Differentiate folders (📁) from files (📄) visually
- Handle edge cases: root level, empty folders, deeply nested paths
Why this question?
Breadcrumb navigation tests your ability to work with tree data structures, manage navigation state as an array (the path), and render dynamic UI based on that state. It's simple enough to implement in 20 minutes but has enough depth for follow-up questions about performance and edge cases.
Component Anatomy
Break the UI into its visual parts before writing code:
Breadcrumb Trail
A horizontal list of clickable segments showing the current path. Each segment (except the last) is a button that navigates back to that folder.
Folder Contents
A list of the current folder's children. Folders are clickable (navigate into), files are not. Shows an empty state if no children.
Folder Item
A single row showing a folder (📁) or file (📄) with its name. Folders have hover states and are buttons; files are static.
Separator
A visual divider (/ or >) between breadcrumb segments. Helps users understand the hierarchy at a glance.
┌─────────────────────────────────────────────────────┐ │ Home / Documents / Work │ ← Breadcrumb trail ├─────────────────────────────────────────────────────┤ │ 📁 Projects │ ← Clickable folder │ 📁 Archive │ ← Clickable folder │ 📄 report.pdf │ ← Non-clickable file │ 📄 notes.txt │ ← Non-clickable file └─────────────────────────────────────────────────────┘ Clicking "Documents" in the breadcrumb → navigates back to Documents Clicking "📁 Projects" → navigates into Projects folder
Data Structure Design
The folder tree is a recursive data structure. Each node can have children that are also nodes. This is the classic tree pattern.
interface FolderItem { id: string; // Unique identifier name: string; // Display name type: "folder" | "file"; children?: FolderItem[]; // Only folders have children }
const FILE_TREE: FolderItem = { id: "root", name: "Home", type: "folder", children: [ { id: "documents", name: "Documents", type: "folder", children: [ { id: "work", name: "Work", type: "folder", children: [ { id: "report", name: "report.pdf", type: "file" }, { id: "slides", name: "slides.pptx", type: "file" }, ], }, { id: "resume", name: "resume.pdf", type: "file" }, ], }, { id: "photos", name: "Photos", type: "folder", children: [ { id: "vacation", name: "vacation.jpg", type: "file" }, ], }, ], };
Key insight: the path is an array
Instead of storing just the current folder, store the entire path as an array: [Home, Documents, Work]. The last element is the current folder. The array itself is the breadcrumb trail. This makes navigation trivial — push to go deeper, slice to go back.
Navigation Logic
Navigation is just two operations on the path array: push (go deeper) and slice (go back). The current folder is always the last element.
Initialize the path
Start with the root folder in the path array: [FILE_TREE]. This means we're at the root level, and the breadcrumb shows just 'Home'.
Navigate into a folder
When the user clicks a folder, push it onto the path array. The path grows: [Home] → [Home, Documents] → [Home, Documents, Work].
Navigate via breadcrumb
When the user clicks a breadcrumb segment at index i, slice the path to keep only elements 0 through i. This 'jumps back' to that folder.
const [path, setPath] = useState<FolderItem[]>([FILE_TREE]); // The current folder is always the last element const currentFolder = path[path.length - 1]; // Navigate into a subfolder — push it onto the path const navigateInto = (folder: FolderItem) => { setPath((prev) => [...prev, folder]); }; // Navigate to a breadcrumb segment — slice the path const navigateTo = (index: number) => { setPath((prev) => prev.slice(0, index + 1)); };
| Action | Path Before | Path After |
|---|---|---|
| Click "Documents" folder | [Home] | [Home, Documents] |
| Click "Work" folder | [Home, Documents] | [Home, Documents, Work] |
| Click "Home" breadcrumb | [Home, Documents, Work] | [Home] |
Why slice(0, index + 1)?
slice(0, index + 1) keeps elements from index 0 up to and including the clicked index. If the user clicks the breadcrumb at index 1 ("Documents"), we keep [Home, Documents] and discard everything after.
Rendering the UI
With the navigation logic in place, rendering is straightforward: map over the path for breadcrumbs, map over currentFolder.children for contents.
Rendering the Breadcrumb Trail
<nav aria-label="Breadcrumb"> <ol className="flex items-center gap-1 text-sm"> {path.map((segment, i) => { const isLast = i === path.length - 1; return ( <li key={segment.id} className="flex items-center gap-1"> {/* Separator (skip for first item) */} {i > 0 && <span className="text-gray-300">/</span>} {/* Last segment is plain text, others are buttons */} {isLast ? ( <span className="text-gray-900 font-medium"> {segment.name} </span> ) : ( <button onClick={() => navigateTo(i)} className="text-gray-500 hover:text-gray-900" > {segment.name} </button> )} </li> ); })} </ol> </nav>
Rendering Folder Contents
<div className="border rounded-lg divide-y"> {currentFolder.children && currentFolder.children.length > 0 ? ( currentFolder.children.map((item) => ( <div key={item.id} className="px-4 py-3 flex items-center gap-3"> {item.type === "folder" ? ( <button onClick={() => navigateInto(item)} className="flex items-center gap-3 w-full text-left" > <span>📁</span> <span>{item.name}</span> </button> ) : ( <div className="flex items-center gap-3 text-gray-500"> <span>📄</span> <span>{item.name}</span> </div> )} </div> )) ) : ( <div className="px-4 py-10 text-center text-gray-400"> This folder is empty. </div> )} </div>
Don't forget unique keys
Use item.id as the key, not the index. If you accidentally use the parent folder's ID (a common bug), React won't re-render the list correctly when you navigate. This is a classic interview gotcha.
Full Implementation
Here's the complete breadcrumb navigation component. Study how the state, navigation functions, and rendering compose together.
"use client"; import { useState } from "react"; interface FolderItem { id: string; name: string; type: "folder" | "file"; children?: FolderItem[]; } const FILE_TREE: FolderItem = { id: "root", name: "Home", type: "folder", children: [ { id: "documents", name: "Documents", type: "folder", children: [ { id: "work", name: "Work", type: "folder", children: [ { id: "report", name: "report.pdf", type: "file" }, { id: "slides", name: "slides.pptx", type: "file" }, ], }, { id: "personal", name: "Personal", type: "folder", children: [ { id: "resume", name: "resume.pdf", type: "file" }, ], }, ], }, { id: "photos", name: "Photos", type: "folder", children: [ { id: "vacation", name: "vacation.jpg", type: "file" }, ], }, ], }; export default function BreadcrumbNavigationPage() { const [path, setPath] = useState<FolderItem[]>([FILE_TREE]); const currentFolder = path[path.length - 1]; const navigateInto = (folder: FolderItem) => { setPath((prev) => [...prev, folder]); }; const navigateTo = (index: number) => { setPath((prev) => prev.slice(0, index + 1)); }; return ( <div className="max-w-2xl mx-auto p-6"> {/* Breadcrumb trail */} <nav aria-label="Breadcrumb" className="mb-6"> <ol className="flex items-center gap-1 text-sm"> {path.map((segment, i) => { const isLast = i === path.length - 1; return ( <li key={segment.id} className="flex items-center gap-1"> {i > 0 && <span className="text-gray-300">/</span>} {isLast ? ( <span className="text-gray-900 font-medium"> {segment.name} </span> ) : ( <button onClick={() => navigateTo(i)} className="text-gray-500 hover:text-gray-900" > {segment.name} </button> )} </li> ); })} </ol> </nav> {/* Folder contents */} <div className="border rounded-lg divide-y"> {currentFolder.children?.length ? ( currentFolder.children.map((item) => ( <div key={item.id} className="px-4 py-3"> {item.type === "folder" ? ( <button onClick={() => navigateInto(item)} className="flex items-center gap-3 w-full" > <span>📁</span> <span>{item.name}</span> </button> ) : ( <div className="flex items-center gap-3 text-gray-500"> <span>📄</span> <span>{item.name}</span> </div> )} </div> )) ) : ( <div className="px-4 py-10 text-center text-gray-400"> This folder is empty. </div> )} </div> </div> ); }
Path as single source of truth
The path array is the only state. The current folder and breadcrumb trail are both derived from it. No redundant state to keep in sync.
Functional state updates
Using setPath(prev => ...) ensures updates are based on the latest state, avoiding stale closure bugs in event handlers.
Lazy loading for large trees
For file systems with thousands of items, fetch children on-demand when a folder is opened instead of loading the entire tree upfront.
Keyboard navigation
Add arrow key support to move focus between items, Enter to navigate into folders, and Backspace to go up one level.
Common Interview Follow-up Questions
After building the breadcrumb navigation, interviewers dig into edge cases and enhancements:
Q:How would you handle a very deep folder structure (20+ levels)?
A: Truncate the breadcrumb trail with an ellipsis. Show the first segment (Home), an ellipsis dropdown with hidden segments, and the last 2-3 segments. Clicking the ellipsis reveals a dropdown menu with all hidden segments.
Q:How would you add URL-based navigation (deep linking)?
A: Store the path in the URL as a query param or path segments (e.g., /files/documents/work). On page load, parse the URL and traverse the tree to rebuild the path array. Use router.push() to update the URL when navigating.
Q:What if the folder tree is fetched from an API?
A: Start with just the root folder. When the user clicks a folder, fetch its children from the API and cache them. Show a loading spinner while fetching. This is lazy loading — you only fetch what the user actually visits.
Q:How would you handle folders with hundreds of items?
A: Add pagination or virtual scrolling. Fetch items in batches (e.g., 50 at a time) with a 'Load more' button or infinite scroll. Use react-window for virtual scrolling if rendering thousands of items.
Q:How would you implement search/filter within a folder?
A: Add a search input above the folder contents. Filter currentFolder.children by name using a case-insensitive includes() check. For deep search across all folders, you'd need to traverse the entire tree or use a server-side search API.
Q:What about accessibility for screen readers?
A: Use semantic HTML: nav with aria-label='Breadcrumb', ol/li for the trail. Add aria-current='page' to the last breadcrumb segment. Use role='list' for folder contents. Ensure all interactive elements are focusable and have accessible names.
Q:How would you add drag-and-drop to move files between folders?
A: Use the HTML5 Drag and Drop API or a library like dnd-kit. Track the dragged item and the drop target folder. On drop, update the tree structure: remove the item from its current parent's children and add it to the target folder's children.
Ready to build it yourself?
We've set up the folder tree and UI shell. Implement the navigation logic and rendering from scratch.
Built for developers, by developers. Happy coding! 🚀