DEV Community

Erhan Tezcan
Erhan Tezcan

Posted on

Advent of TypeScript 2023 - Part. I

Advent of TypeScript 2023 is a series of challenges related to type-level TypeScript.

This page provides walkthroughs for days 1 to 10.

You can find the solutions & tests at

Day 1. Christmas Cookies

We simply have to provide a Union type, as follows:

type SantasFavoriteCookies = "ginger-bread" | "chocolate-chip";
Enter fullscreen mode Exit fullscreen mode

Day 2. Cookie Inventory

We are required to return the keys in the given object, thankfully keyof operator does just that!

type CookieSurveyInput<T> = keyof T;
Enter fullscreen mode Exit fullscreen mode

Day 3. Gift Wrapper

We have 3 parameters, and in the given order they must be assigned as values to keys present, from and to. Well, working with objects in the type-world is very similar to JS, as you can see:

type GiftWrapper<P, F, T> = {
  present: P;
  from: F;
  to: T;
Enter fullscreen mode Exit fullscreen mode

Day 4. Delivery Addresses

Here, we need to change the values of a given object. We can access the keys of an object via K in keyof T; remember keyof T returns a union and in tells us that we want all the values in the union. Our solution is therefore:

type Address = { address: string; city: string };
type PresentDeliveryList<T extends Record<PropertyKey, any>> = { [K in keyof T]: Address };
Enter fullscreen mode Exit fullscreen mode

We don't really need T extends Record<PropertyKey, any> here for the tests, but that provides a bit more type-safety where unsuitable parameters to PresentDeliveryList would cause an error.

Day 5. Santa's List

Here we are asked to concatenate two arrays. We must remember that spread operator ...array works in type-world too! So, if we have two arrays A, B we can spread them into a single array [...A, ...B] and the result will be as if we had called A.concat(B).

type SantasList<A extends readonly any[], B extends readonly any[]> = [...A, ...B];
Enter fullscreen mode Exit fullscreen mode

Day 6. Filtering the Children I

We are asked to exclude an item from a union. TypeScript has a built-in called Exclude just for that!

type FilterChildrenBy<T, E> = Exclude<T, E>;
Enter fullscreen mode Exit fullscreen mode

Day 7. Filtering the Children II

We have worked with changing the value of a record before in day 4; now, we are asked to change the keys! This is a bit more work, but we will make use of something called key remapping which allows one to re-map keys in a mapped type.

type GoodProperty<K extends PropertyKey> = K extends string ? `good_${K}` : never;

type AppendGood<T extends Record<string, any>> = { [K in keyof T as GoodProperty<K>]: T[K] };
Enter fullscreen mode Exit fullscreen mode

One thing to notice here:

type AppendGood<T extends Record<string, any>> = { [K in keyof T as `good_${K}`]: T[K] };

The code above will not work because a string literal accepts things that can be stringified, however a property (via keyof) can return a Symbol which wouldn't be compatible with our string literal there.

I didn't know about key-remapping until this challenge!

Day 8. Filtering the Children III

In this challenge, we will filter out some of the keys based on how they fit a given string. We can use Exclude for this as well! If you hover over the built-in Exclude you will see that it is written as:

type Exclude<T, U> = T extends U ? never : T;
Enter fullscreen mode Exit fullscreen mode

So if a given string T extends U, it will return never. If a mapped-key is never it is not included in the resulting object. With this in mind, our solution is:

type RemoveNaughtyChildren<T> = { [K in Exclude<keyof T, `naughty_${string}`>]: T[K] };
Enter fullscreen mode Exit fullscreen mode

Notice how we access the respective values of each key via T[K]

Day 9. Is Santa Dyslexic?

We are asked to reverse a string, at type-level! Although this may sound scary at first, it is not so scary when you have knowledge of the infer keyword. Inferring is done within a conditional type, you can learn more about it here.

We can ask if a type extends some other type, and infer parts of it based on whether that condition is true or not! In this challenge, we can ask if our type extends a string with two inferred parts: T extends ${infer F}${infer S}. It just turns out that F will be the first character here, and S will be the rest of the string.

So, we can extract F and S like that, and put F at the end of a new string, prefixed by Reverse<S> yet again to reverse the remaining string. In the end, there will be no characters left, and at that point we will return the string itself.

type Reverse<T extends string> = T extends `${infer F}${infer S}` ? `${Reverse<S>}${F}` : T;
Enter fullscreen mode Exit fullscreen mode

When T = 'a', that condition will result in F = 'a' and S = ''.

Day 10. Street Suffix

In this challenge, we just need to make a suffix check, a job suited for the beloved condition-type together with a string literal.

type StreetSuffixTester<T extends string, S extends string> = T extends `${string}${S}` ? true : false;
Enter fullscreen mode Exit fullscreen mode

The code is pretty self-explanatory in this one.

Top comments (0)