This has to be my favorite Typescript feature 🤯🤯🤯
5 min read
Happy New Year! 🎉🎉🎉
// eslint-disable-next-line @typescript-eslint/no-explicit-any
I love Typescript so much, no matter how much svelte creators hate it. I’ve been using it for a while now and it’s a very important tool to have in your hand while building modern web applications. Because of TS, I dont have to wait for the browser to tell me that I’ve made a mistake, instead TS will yell at me immediately that I’m a terrible developer at remembering basic Javascript object properties. But you are not here for a typescript tutorial here, this is not a TS 101 blog post. I’m here to show you my favorite TS feature that I’ve been using for a while now.
Recursion
Recursion is a very important concept in programming. It’s a technique where a function calls itself. It’s a very powerful technique that can be used to solve many problems. I’m not going to go into details about recursion, but if you want to learn more about it, I recommend you to check out this article.
I didnt know that TS type inferring goes beyond just static type analysis but it can pretty much extend to the programming paradigms such as recursions. And with the help of this and other typescript features such as string literal types and type generics, we can create some pretty cool stuff.
Here’s how
Suppose you are a senior engineer in a company that builds a lot of cool tech, and you are paged on multiple JIRA issues but this is your favorite time of the year, Its the new intern joining time, and ofcourse you want to flex your TS skills on them to gain the respect and the status of wizard in the company, and you are asked to reverse a string but on the type level. You can do this by using recursion.
type DemoString = "Hello-World";
type ReverseString<
S extends string,
Result extends string = ""
> = S extends `${infer FirstChar}${infer Rest}`
? ReverseString<Rest, `${FirstChar}${Result}`>
: Result;
type Reversed = ReverseString<DemoString>;
// ^? "dlroW-olleH"
Okay so what has happened here? Let’s break it down.
- We have created a type called
DemoStringwhich is a string literal type. - We have created a type called
ReverseStringwhich takes two arguments,Swhich is a string literal type andResultwhich is also a string literal type but with a default value of an empty string. - We have created a conditional type which checks if the
Stype extends a string literal type with two infered types,FirstCharandRest. - If the condition is true, we recursively call the
ReverseStringtype with theResttype and theFirstChartype is prepended to theResulttype. - If the condition is false, we return the
Resulttype.
Another method to do this is by using the following trick:
type DemoString = "Hello-World";
type ReverseString<S extends string> =
S extends `${infer FirstChar}${infer Rest}`
? `${ReverseString<Rest>}${FirstChar}`
: S;
type Reversed = ReverseString<DemoString>;
// ^? "dlroW-olleH"
Here:
- We have created a type called
DemoStringwhich is a string literal type. - We have created a type called
ReverseStringwhich takes one argument,Swhich is a string literal type. - We have created a conditional type which checks if the
Stype extends a string literal type with two infered types,FirstCharandRest. - If the condition is true, we recursively call the
ReverseStringtype with theResttype and theFirstChartype is appended to the result of theReverseStringtype.
Did you like the idea? Okay wait so now let’s try to do something cooler with this, finding the length of the string, only using the typescript okay.
Now here’s how we will do it.
- First We will create Array out of the string which will have items equal to the length of the string.
- Then we will get the length of the array.
So let’s do it:
type DemoString = "Hello-World";
/**
* Create an array out of the string
*/
type ArrayOf<
S extends string,
Result extends Array<any> = []
> = S extends `${infer FirstChar}${infer Rest}`
? toArray<Rest, [...Result, any]>
: Result;
/**
* Get the length of the array
*/
type LengthOf<A extends Array<any>> = A extends { length: infer L } ? L : never;
type LengthOfString<S extends string> = LengthOf<ArrayOf<S>>;
// ^? 11
As we can see, here we have:
- Created a type called
ArrayOfwhich takes two arguments,Swhich is a string literal type andResultwhich is an array literal type with a default value of an empty array. - We have created a conditional type which checks if the
Stype extends a string literal type with two infered types,FirstCharandRest. - If the condition is true, we recursively call the
ArrayOftype with theResttype and theFirstChartype is prepended to theResulttype. - If the condition is false, we return the
Resulttype. - We have created a type called
LengthOfwhich takes an array literal type and returns the length of the array. - We have created a type called
LengthOfStringwhich takes a string literal type and returns the length of the string.
Conclusion
I hope you loved the idea of using recursion in typescript. I’ve been using this pattern for a while now and it has helped me a lot in my type driven development, (my definition of TDD)