๐ Mastering Array Flattening in JavaScript: From Built-ins to Custom Polyfills

If you've spent any time working with JavaScript, you've probably encountered a situation where you had an array filled with other arrays, and you just wanted one clean, flat list.
Flattening arrays is one of those concepts that seems incredibly simple on the surface, but it has a funny way of showing up everywhereโfrom everyday data transformation tasks to the most rigorous coding interviews.
In this guide, we're going to break down array flattening step-by-step. We'll explore why you might need it, the built-in ways to do it, and, most importantly, how to write your own custom flattening functions when the built-in tools aren't available or aren't enough.
๐ฆ Wait, What Are Nested Arrays?
Before we flatten anything, let's make sure we're on the same page. A nested array (or multidimensional array) is simply an array that contains other arrays as its elements.
Think of it like a set of nesting dolls or boxes within boxes.
const nestedArray = [1, [2, 3], [4, [5, 6]]];
If we were to map this out visually, it would look something like a tree of values:
[
1,
[2, 3],
[4, [5, 6]]
]
Instead of a simple, flat list of numbers, our elements are grouped in sub-arrays at varying depths.
๐ค Why Do We Need to Flatten Them?
Flattening is the process of taking all those nested elements and extracting them into a single-level, flat array. Taking our previous example:
Input: [1, [2, 3], [4, [5, 6]]]
Output: [1, 2, 3, 4, 5, 6]
You might be wondering, "When would I actually need to do this in the real world?"
There are plenty of practical use cases:
Cleaning up API responses: Sometimes the data you receive from a backend is heavily nested and difficult to iterate over cleanly.
Working with complex forms: Handling inputs that have grouped values or sub-categories.
Component rendering in UI frameworks: Frameworks like React often need a flat list of items to render efficiently.
Handling file systems or tree-like data: Extracting a list of all files from a directory tree.
Think of flattening as taking everything out of those nested boxes and laying them all out side-by-side on a single table.
๐ The Easy Way: Using Built-in .flat()
Modern JavaScript provides an incredibly convenient, built-in method for this: Array.prototype.flat().
const arr = [1, [2, 3], [4, [5, 6]]];
console.log(arr.flat(2));
// Output: [1, 2, 3, 4, 5, 6]
How it works:
The .flat() method takes an optional argument called depth, which specifies how many levels of nesting you want to flatten.
By default,
depthis1. This means it will only flatten one level down.If you have deeply nested arrays and want to completely flatten them, you can pass
Infinityas the depth!
// Completely flatten an array of any depth
const deeplyNested = [1, [[[2]]]];
console.log(deeplyNested.flat(Infinity)); // [1, 2]
As straightforward as .flat() is, there's a catch. Interviewers love asking you to solve this problem without using the built-in method. Why? Because it tests your understanding of recursion, iteration, and data structures. Let's look at how to build it from scratch.
๐ The Classic Interview Answer: Recursion
If you're in a technical interview and you're asked to flatten an array, this is generally the approach they are looking for. Recursion is perfect here because an array could potentially have smaller arrays inside it, which might have smaller arrays inside them... and so on.
function flattenArray(arr) {
let result = [];
for (let item of arr) {
// If the current item is an array, we need to dig deeper!
if (Array.isArray(item)) {
result = result.concat(flattenArray(item)); // Recursive call
} else {
// If it's not an array, just add it to our result list
result.push(item);
}
}
return result;
}
Breaking it down:
We iterate through every element in the array.
We check if the element is an array itself using
Array.isArray().If it is an array, we call our
flattenArrayfunction again on that sub-array. This is the recursion!Once we get the flattened result of that sub-array, we merge it into our main
resultarray using.concat().If the element is just a regular value (like a number), we push it directly into the result.
๐งฉ The Functional Approach: Using reduce()
Functional programming concepts are highly valued in modern JS. We can rewrite our recursive function using reduce(...), which makes for a very clean and concise solution.
function flattenWithReduce(arr) {
return arr.reduce((acc, currentItem) => {
return acc.concat(
Array.isArray(currentItem) ? flattenWithReduce(currentItem) : currentItem
);
}, []);
}
This functions exactly the same as the recursion approach above, but it uses the accumulator (acc) pattern. Whenever you have to turn an array into a single "accumulated" value (even if that single value is another array), reduce() is usually a great tool for the job.
โก The Iterative Approach: Using a Stack
Recursion is elegant, but it can hit call stack limits with massive, heavily nested datasets. If you want to impress an interviewer, show them you can do it iteratively using a "Stack".
function flattenIterative(arr) {
// We initialize our stack with the contents of the original array
const stack = [...arr];
const result = [];
// While there are still items to process in the stack
while (stack.length > 0) {
const next = stack.pop();
if (Array.isArray(next)) {
// If it's an array, push its items back onto the stack to be processed
stack.push(...next);
} else {
// If it's a final value, add it to the front of our result
result.unshift(next);
}
}
return result;
}
This approach manually manages the un-nesting process, avoiding the memory overhead of multiple function calls.
๐ฏ Bonus Challenge: Writing a Custom .flat(depth) Polyfill
Want to go the extra mile? Let's recreate the exact behavior of Array.prototype.flat(), including the depth parameter!
function customFlat(arr, depth = 1) {
// Base case: If we've reached 0 depth, just return the current array
if (depth === 0) return arr;
return arr.reduce((acc, item) => {
if (Array.isArray(item)) {
// We found a sub-array! Flatten it, and decrease the remaining depth by 1
return acc.concat(customFlat(item, depth - 1));
}
// It's a normal value, just append it
return acc.concat(item);
}, []);
}
console.log(customFlat([1, [2, [3, 4]]], 1)); // [1, 2, [3, 4]]
console.log(customFlat([1, [2, [3, 4]]], 2)); // [1, 2, 3, 4]
This is the ultimate interview flex. You're combining functional concepts, recursion, and API understanding all into one neat function.
๐ Watch out for these Edge Cases
When tackling these problems, remember to test your functions against weird edge cases:
Empty arrays: How does your function handle
[]?Extreme deep nesting: Does it crash on
[[[[[1]]]]]?Mixed Data Types: Keep in mind that arrays can hold strings, null, booleans, and objects:
[1, "hello", [true, [null, {}]]]
๐ Final Thoughts
Flattening arrays is much more than just a convenience utility or a trivia question. Mastering these custom implementations forces you to think clearly about trees, recursion, and how data structures relate to one another.
If you are currently preparing for an interview, here's my biggest tip: Close your editor, grab a piece of paper, and try to write the recursive array flattener from memory. Once you can explain it to an interviewer smoothly, you'll be one step closer to acing your JavaScript fundamentals! ๐




