Recursion with Template Literals in TypeScript

ยท

3 min read

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 type S must be a string.

  • S extends `${infer First}${infer Rest}` ? ... : '': This is a conditional type that checks if S can be deconstructed into two parts, the first character First and the rest of the string Rest. 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:

  1. 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) with infer to allow the template literal.

  2. Rest: This represents the rest of the input string, excluding the first character. Again we need to make sure Rest is of type string with infer.

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 ๐Ÿฆ

ย