10 Lines of Code: The Flat-to-Tree Algorithm Every Frontend Dev Needs
Written in front: Today I learned a super common algorithm in real work — list to tree. The backend queries
select * from menufrom the database and gets a one-dimensional flat list, but the frontend needs to display a tree structure (multi-level menus, address selectors, organizational charts...). How to convert? The teacher taught two tricks: the Map method and the reduce method. After listening, I just want to say: it turns out to be so simple, and I used to write recursion by hand for a long time!
I. Why do we need "list to tree"?
1.1 The data the backend gives looks like this
The teacher gave a very realistic example:
const flatList = [
{ id: 1, name: 'Level 1 Menu A', parentId: 0 },
{ id: 2, name: 'Level 1 Menu B', parentId: 0 },
{ id: 3, name: 'Level 2 A-1', parentId: 1 },
{ id: 4, name: 'Level 3 A-1-1', parentId: 3 },
{ id: 5, name: 'Level 2 B-1', parentId: 2 },
]
This is a bunch of flat data — all items are in the same array, with no nesting relationship. Each item has a parentId, indicating who its parent node is.
parentId: 0means "I am the root node, I have no parent."parentId: 1means "My parent is the node with id 1."
1.2 The data the frontend wants looks like this
But frontend components (like Element UI's Tree, Ant Design's Cascader) need a tree structure:
[
{
"id": 1,
"name": "Level 1 Menu A",
"parentId": 0,
"children": [
{
"id": 3,
"name": "Level 2 A-1",
"parentId": 1,
"children": [
{
"id": 4,
"name": "Level 3 A-1-1",
"parentId": 3,
"children": []
}
]
}
]
},
{
"id": 2,
"name": "Level 1 Menu B",
"parentId": 0,
"children": [
{
"id": 5,
"name": "Level 2 B-1",
"parentId": 2,
"children": []
}
]
}
]
From flat to tree — this is the problem "list to tree" solves.
The teacher mentioned that this requirement is especially common in admin backends:
- Multi-level menus
- Three-level address pickers (province → city → district)
- Organizational charts
- Product categories
MySQL stores flat structures; select * from returns a one-dimensional array. So list-to-tree is an almost mandatory data processing task in front-end/back-end separation projects.
II. Method 1: Map method — space for time
2.1 Core idea
The first method the teacher taught uses the ES6 Map data structure:
"ES6 new data structure HashMap"
Core idea: First store all nodes in a Map (id → node), then traverse once more, attaching each node under its corresponding parent node.
2.2 Code implementation
function listToTree(list) {
const map = new Map(); // HashMap, id -> node
const tree = [];
// First pass: put all nodes into the Map, and initialize children
list.forEach((item) => {
map.set(item.id, {
...item, // spread original properties
children: [] // add an empty array
});
});
// Second pass: attach each node under its parent node
list.forEach((item) => {
const current = map.get(item.id); // current item
const parent = map.get(item.parentId); // parent node of current item
if (parent) {
parent.children.push(current); // has parent, attach
} else {
tree.push(current); // no parent, it's a root node
}
});
return tree;
}
2.3 Execution process breakdown
Using the example data, see how the code runs:
First pass: Build the Map
Map {
1 → { id: 1, name: 'Level 1 Menu A', parentId: 0, children: [] }
2 → { id: 2, name: 'Level 1 Menu B', parentId: 0, children: [] }
3 → { id: 3, name: 'Level 2 A-1', parentId: 1, children: [] }
4 → { id: 4, name: 'Level 3 A-1-1', parentId: 3, children: [] }
5 → { id: 5, name: 'Level 2 B-1', parentId: 2, children: [] }
}
Second pass: Attach to parent
| Current item | parentId | Parent exists? | Operation |
|---|---|---|---|
| id: 1 | 0 | map.get(0) = undefined | tree.push(node1) |
| id: 2 | 0 | map.get(0) = undefined | tree.push(node2) |
| id: 3 | 1 | map.get(1) = node1 | node1.children.push(node3) |
| id: 4 | 3 | map.get(3) = node3 | node3.children.push(node4) |
| id: 5 | 2 | map.get(2) = node2 | node2.children.push(node5) |
Final result:
tree = [node1, node2]
node1.children = [node3]
node3.children = [node4]
node2.children = [node5]
2.4 Time complexity: O(n)
Two traversals, each O(n), total time complexity O(n). Uses a Map for space-for-time, parent lookup is O(1).
It's like organizing a family tree:
- First line everyone up by their ID number (id) in a Map.
- Then ask each one "Who is your father?" (parentId), and stand behind the father (children.push).
III. Method 2: reduce method — elegance of functional programming
3.1 Core idea
The second method the teacher taught uses reduce, more functional and concise:
function listToTree(list) {
// First reduce: build nodeMap
const nodeMap = list.reduce((map, item) => {
map[item.id] = { ...item, children: [] };
return map;
}, {});
// Second reduce: assemble tree
return list.reduce((tree, item) => {
const cur = nodeMap[item.id];
const parent = nodeMap[item.parentId];
if (parent) {
parent.children.push(cur);
} else {
tree.push(cur);
}
return tree;
}, []);
}
3.2 The magic of reduce
reduce is the array's "universal folder" — it folds an array into a single value.
First reduce:
- Initial value is an empty object
{}. - Each iteration uses
item.idas key and{ ...item, children: [] }as value, storing into map. - Finally get
nodeMap = { 1: node1, 2: node2, ... }.
Second reduce:
- Initial value is an empty array
[]. - Each iteration determines whether the current node has a parent:
- Has parent →
parent.children.push(cur). - No parent →
tree.push(cur).
- Has parent →
- Finally returns the assembled tree.
3.3 Map vs reduce: two styles, same efficiency
| Comparison | Map method | reduce method |
|---|---|---|
| Code style | Imperative (forEach) | Functional (reduce) |
| Readability | Intuitive, clear steps | Concise, one operation per line |
| Time complexity | O(n) | O(n) |
| Space complexity | O(n) (Map) | O(n) (object) |
| Applicable scenario | Team prefers imperative | Team prefers functional |
The core idea of both methods is exactly the same: first build an index, then attach parent-child relationships. Only the implementation differs.
IV. Key knowledge point: parentId is the "DNA" of the tree
4.1 Why is parentId so important?
The teacher emphasized:
"
parentIdis the key to the tree."
In databases, storing tree structures usually has two schemes:
| Scheme | Storage method | Advantages | Disadvantages |
|---|---|---|---|
| Adjacency List | Each node stores parentId |
Simple and intuitive, easy insert/delete | Querying subtrees requires recursion |
| Nested Set | Stores left and right |
Fast subtree queries | Insert/delete is troublesome |
parentId is the core of the adjacency list scheme. Most admin backends use this scheme because it's simple, intuitive, and easy to maintain.
4.2 Flat vs tree: two perspectives
Flat perspective (database): Tree perspective (frontend):
[id:1, parentId:0] Level 1 Menu A
[id:2, parentId:0] ├── Level 2 A-1
[id:3, parentId:1] │ └── Level 3 A-1-1
[id:4, parentId:3] └── Level 2 B-1
[id:5, parentId:2]
Databases like flat (fast queries, saves storage), frontends like tree (intuitive display, easy recursion). List-to-tree is the conversion between these two perspectives.
V. The magic of JSON.stringify: printing tree structures
The teacher also showed a debugging trick:
console.log(JSON.stringify(listToTree(flatList), null, 2))
JSON.stringify(obj, null, 2) can format an object into a string with indentation.
- First parameter: the object to serialize.
- Second parameter:
null(no replacement). - Third parameter:
2(indent 2 spaces).
This makes the tree structure very clear in the console, much better than directly console.log a bunch of [object Object].
It's like writing an article:
console.log(obj): all text squeezed into one line, headache-inducing.JSON.stringify(obj, null, 2): automatically breaks lines and indents, like a well-formatted article, clear at a glance.
VI. Summary: List to tree, a "basic skill" every frontend must master
| Knowledge point | Explanation |
|---|---|
| List to tree | Convert flat array to nested tree structure |
parentId |
Key field identifying parent node |
| Map method | Use HashMap as index, two traversals |
| reduce method | Use reduce to fold array, functional style |
| Time complexity | O(n), linear traversal |
| Application scenarios | Multi-level menus, address selection, organizational charts |
JSON.stringify(obj, null, 2) |
Format and print objects |
List to tree is a high-frequency frontend interview question and a skill used daily in real work. Mastering the Map method and reduce method, no matter which style the interviewer asks, you can handle it calmly.
Written at the end
Today's biggest gain is understanding the idea of "space for time." Using Map as an index optimizes O(n²) nested lookup into O(n) linear traversal — this idea can be used in many algorithm problems.
Next time an interviewer asks you: "How to convert a flat array into a tree structure?"
You can calmly say:
"I use Map as an index, two traversals. First pass: put all nodes into a Map (id → node) and initialize the children array. Second pass: according to parentId, find the parent node, push the current node into the parent's children. If parentId cannot find a corresponding node, it's a root node, push directly into the result array. Time complexity O(n). Can also use reduce for a more functional style."
Then watch the interviewer's satisfied expression, and silently think: This round, I'm safe again.
All code examples in this article come from classroom learning materials, real and runnable.