beraliv

Flatten tuple type of tuples in TypeScript

Example of Flatten use
1type Flatten<T> = any; // implementation
2
3type Step1 = Flatten<[1, [2, [3, [4]]]]>;
4type Step2 = [1, ...Flatten<[2, [3, [4]]]>];
5type Step3 = [1, 2, ...Flatten<[3, [4]]>];
6type Step4 = [1, 2, 3, ...Flatten<[4]>];
7type Result = [1, 2, 3, 4];

Today we discuss Flatten

It works the same way as Array.prototype.flat when you pass Infinity

Let's find out how to do that in TypeScript 💪

Iteration over tuple elements

Knowing the approach from different challenges, as we want to save the structure (it will be tuple at the end), we apply Type inference in conditional types with Rest elements in Tuples.

We iterate over elements:

Iterate over tuple elements
1type Flatten<T> = T extends []
2 ? []
3 : T extends [infer Head, ...infer Tail]
4 ? [] // make it flatten using Head and Tail
5 : [];

Then we have 2 cases:

  1. If the element is a tuple, we apply changes recursively
  2. Otherwise, we leave it as is

Let's add second case:

Put element to the result tuple type
1type Flatten<T> = T extends []
2 ? []
3 : T extends [infer Head, ...infer Tail]
4 ? [Head, ...Flatten<Tail>]
5 : [];

Call it recursively when needed

At the moment, if we have a look at Playground – https://tsplay.dev/w18bXW, we will find that not all tests are passed.

We forgot to apply function recursively when we have an element as tuple. Let's have an example here:

Example where Flatten isn't working
1type Step1 = Flatten<[1, [2]]>;
2type Step2 = [1, ...Flatten<[[2]]>];
3// ❌ Expected to have [1, 2] instead
4type Result = [1, [2]];

In this case we cannot just add it to result tuple, we need to call Flatten before and then put all the elements of it to the result type. Let's change the implementation based on that:

Solution
1type Flatten<T> = T extends []
2 ? []
3 : T extends [infer Head, ...infer Tail]
4 ? Head extends any[]
5 ? [...Flatten<Head>, ...Flatten<Tail>]
6 : [Head, ...Flatten<Tail>]
7 : [];

Now it's working as expected 🔥

Check out Playground – https://tsplay.dev/WKk0DW

And last but not least, as you see we return empty array [] in several places, let's simplify solution a little bit:

Shorter solution
1type Flatten<T> = T extends [infer Head, ...infer Tail]
2 ? Head extends any[]
3 ? [...Flatten<Head>, ...Flatten<Tail>]
4 : [Head, ...Flatten<Tail>]
5 : [];

Shorter solution – https://tsplay.dev/WP7gzm

Thank you for your time and have a productive upcoming week 🚀

typescript
Alexey Berezin profile image

Written by Alexey Berezin who loves London 🏴󠁧󠁢󠁥󠁮󠁧󠁿, players ⏯ and TypeScript 🦺 Follow me on Twitter