Deep flatten nested array
typescriptA fully type-safe TypeScript function that recursively flattens deeply nested arrays of arbitrary depth, preserving the element type through advanced conditional type inference.
Code
type DeepFlatten<T> = T extends readonly (infer U)[]
? DeepFlatten<U>
: T;
type NestedArray<T> = T | readonly NestedArray<T>[];
function deepFlatten<T>(arr: NestedArray<T>[]): DeepFlatten<T>[] {
const result: DeepFlatten<T>[] = [];
function flatten(item: NestedArray<T>): void {
if (Array.isArray(item)) {
for (const element of item) {
flatten(element);
}
} else {
result.push(item as DeepFlatten<T>);
}
}
for (const item of arr) {
flatten(item);
}
return result;
}
// Examples with full type inference
const nested1 = [1, [2, [3, [4, [5]]]], 6];
const flat1 = deepFlatten(nested1);
console.log(flat1); // [1, 2, 3, 4, 5, 6] - type: number[]
const nested2 = ["a", ["b", ["c", "d"]], [["e"]]];
const flat2 = deepFlatten(nested2);
console.log(flat2); // ["a", "b", "c", "d", "e"] - type: string[]
const nested3 = [[[[true]]], false, [[[[[true]]]]]];
const flat3 = deepFlatten(nested3);
console.log(flat3); // [true, false, true] - type: boolean[]
// Works with objects too
interface User { name: string; id: number; }
const users: NestedArray<User>[] = [
{ name: "Alice", id: 1 },
[{ name: "Bob", id: 2 }, [{ name: "Charlie", id: 3 }]]
];
const flatUsers = deepFlatten(users);
console.log(flatUsers); // type: User[]
// Empty arrays handled correctly
const empty = deepFlatten([[], [[]], [[[]]]]);
console.log(empty); // [] - type: never[]How It Works
The DeepFlatten<T> type is the heart of this solution. It's a recursive conditional type that keeps unwrapping array types until it reaches a non-array type. When given number[][], it first infers number[] as U, then recursively processes that to get number. This recursion continues until T is no longer an array, at which point it returns T directly. The readonly modifier ensures compatibility with both mutable and immutable arrays.
The NestedArray<T> helper type defines the structure of our input: either a value of type T or an array of NestedArray<T>. This recursive type definition allows for arbitrary nesting depth while maintaining type safety. Using readonly here makes the type covariant, accepting both T[] and readonly T[] inputs without issues.
The implementation uses an inner flatten function with a closure over the result array for efficiency. Rather than creating new arrays at each recursion level (which would be O(n²) in memory allocations), we push directly to a single result array. The Array.isArray() check is the standard way to detect arrays in JavaScript, handling all array types including those from different realms (iframes).
One important edge case is empty nested arrays like [[[]]]. These are handled correctly—the function simply doesn't push anything to the result. The type inference for empty arrays results in never[], which is TypeScript's way of saying "an array that can't contain anything," and this correctly unifies with any other array type when combined.
Use this pattern when processing hierarchical data structures that need to be normalized, such as file system trees, nested comment threads, or recursive menu structures. Avoid it when you need to preserve the nesting structure or when working with very deep nesting (thousands of levels) where the recursive type might hit TypeScript's recursion limit. For shallow flattening (1-2 levels), the built-in Array.prototype.flat() method with a depth parameter is more performant.