Recursion with Template Literals in TypeScript
Table of contents
All right this is the second week of our holiday TypeScript challenges, and we are going to start it with a very spicy one ๐ถ๏ธ. Things are moving from beginner to intermediate, I'm pretty sure you are going to feel like a TS hero after we complete this one.
Let's get into it!
Day nine - Dec 9th
I strongly encourage you to try the first challenge on your own before reading the solution here.
Challenge: Is Santa Dyslexic?
The code to complete
type Reverse = unknown;
The tests
import { Expect, Equal } from 'type-testing';
type test_0_actual = Reverse<'rehsaD'>;
type test_0_expected = 'Dasher';
type test_0 = Expect<Equal<test_0_expected, test_0_actual>>;
type test_1_actual = Reverse<'recnaD'>;
type test_1_expected = 'Dancer';
type test_1 = Expect<Equal<test_1_expected, test_1_actual>>;
The solution
Hope you are ready for this... the solution involves... recursion ๐คฏ.
To solve today's prompt we need to use template literals and recursion to reverse the input string:
type Reverse<S extends string> =
S extends `${infer First}${infer Rest}` ?
`${Reverse<Rest>}${First}` : '';
For the base case of the recursion, we have an empty string, and the reversal is done by appending the first character to the reversed rest of the string.
Here is a break down of what's happening:
We define
Reverse
as a generic type.S extends string
: The input typeS
must be a string.S extends `${infer First}${infer Rest}` ? ... : ''
: This is a conditional type that checks ifS
can be deconstructed into two parts, the first characterFirst
and the rest of the stringRest
. If it can, it then moves to the recursive case, otherwise, it returns an empty string (''
) our base base.${Reverse<Rest>}${First}
: This is our recursion step where it reverses the rest of the string (Rest
) and appends the first character (First
) to it. This continues until the base case is reached.
Infer, First and Rest
There were a couple of interesting things happening aside from the recursion. Let's start with infer
. The infer
keyword works in conditional types, specifically within the extends
clause. It allows TypeScript to figure out or guess a type based on what's happening inside the true
part of the condition.
type LastElement<T extends any[]> =
T extends [...any[], infer Last] ? Last : never;
type Last = LastElement<[string, number, boolean]>
// type Last = boolean;
As you can see in this example, we are only using infer
after extends
and is helping us figure out the type of the Last
element in the Array.
We are also leveraging template literals in this solution, which are used to manipulate string laterals in a type-safe way. When we are doing:
${infer First}${infer Rest}
We are just deconstructing the input string into two parts:
First
: It represents the very first thing in a group, in this case, the first letter in a word. We need to capture its type (string) withinfer
to allow the template literal.Rest
: This represents the rest of the input string, excluding the first character. Again we need to make sure Rest is of type string withinfer
.
Summary
We learned a couple of different new TypeScript fundamentals today, this a quick summary ๐ :
Infer - helps deduce or figure out a type
First & Last - placeholders for the initial or last part or element in a sequence
Rest - placeholder for the remaining elements or characters after the first one
Recursion in types
This was definitely an interesting one, which takes us one step closer to the double digits, Iโm excited to see you tomorrow on the 10th challenge.
And remember that TypeScript is back in town ๐ต๐.
If you like this content consider checking out what I post on Twitter/X ๐ฆ
Subscribe to my newsletter
Read articles from Fernando De La Madrid directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Fernando De La Madrid
Fernando De La Madrid
Bury the past, code the future!