Explain Codes LogoExplain Codes Logo

Build tree array from flat array in JavaScript

javascript
prompt-engineering
functions
performance
Anton ShumikhinbyAnton Shumikhin·Jan 30, 2025
TLDR

Our task is to transform a flat array into a nested tree structure. The strategy here involves creating a map to link node IDs to array indices for quick lookup, then executing a loop over the array to place each node in its parent's children array. Nodes that lack a parent_id are the root nodes.

Refer to the quick example below:

function buildTree(items) { let map = {}, node, tree = [], i; for (i = 0; i < items.length; i++) { map[items[i].id] = i; // Map each node's ID to its index items[i].children = []; // Start your engines, kiddos } for (i = 0; i < items.length; i++) { node = items[i]; if (node.parent_id) { // if node has a parent, it's time to fly to the nest items[map[node.parent_id]].children.push(node); } else { // if node is a root, welcome to the tree-house tree.push(node); } } return tree; } const items = [ { id: 1, name: 'Root1', parent_id: null }, { id: 2, name: 'Child1', parent_id: 1 }, { id: 3, name: 'Child2', parent_id: 1 } ]; console.log(buildTree(items));

This example generates a nested hierarchy with minimal overhead, producing a usable tree structure in no time.

Enhancing efficiency and managing special cases

Handling large datasets requires a keen sense of efficiency. Our solution holds a time complexity of Θ(n log(n)), suggesting decent performance with larger datasets. But what about small datasets? In such cases, a recursive filter could be a rockstar. Don't forget to initialize your map and children arrays before looping through them – a coder's time is precious!

While constructing nested trees, be wary of the dangerous "dangling branches" – nodes that hold a parent_id not found in your set. Lost in the woods, these nodes need rescue. This way, no node gets left behind in the transformation.

Considering alternative solutions

Taking the recursive route

Smaller datasets might call for a simplified approach. Consider a recursive solution for such instances:

function unflatten(array, parent_id = null) { return array .filter(item => item.parent_id === parent_id) .map(item => ({ ...item, children: unflatten(array, item.id) })); // Who knew recursion could be so...recursive? }

When roots multiply

If you're dealing with a forest of roots, be sure to accommodate them all in your final tree:

items.filter(item => item.parent_id === null).forEach(rootNode => tree.push(rootNode));

Error checks and validation

Ensure your data is prepared for any weather. Handle cases where entries might hold parent_id that doesn't exist in the array:

if (node.parent_id && !map[node.parent_id]) { console.warn(`Parent ID (${node.parent_id}) not found in map for Node ID (${node.id}). Houston, we have a problem.`); }

Embracing the future with modern JavaScript

The spread operator from ES6 can turn your code from "meh" to "wow" with its readability and brevity. At the same time, steering your lookup operations with the efficient Map data structure can lead to significant improvements:

const itemMap = new Map(items.map(item => [item.id, { ...item, children: [] }])); // An efficient map function? Now that's what I'm Tolkien about!

Visualization

Now, let's visualize our transformation:

A flat array (📿): [child, parent, grandparent]

A transform function (🛠️): Link child to parent to grandparent

Resulting in a multi-layered tree (🌳🔒):

          [ grandparent ]
               /    \
          [ parent ] [ uncle ]
              /
        [ child ]

Strikingly clear, beautifully nested. That's our elegantly structured tree.

Keeping our solution self-reliant

While some might be tempted to use a library like underscore.js or lodash, our goal is to keep our solution self-contained. Keeping our code library-free makes it independent, manageable, and simple. Indeed, a beautiful tree doesn't need external decorations.

For the sake of performance

Big O isn't just an impressive name, it's our performance yardstick. For large datasets, consider a single-pass for loop against a forEach for better speed and efficiency. And of course, always validate your solution with various datasets.