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 https://github.com/erhant/aot-2023
Day 1. Christmas Cookies
We simply have to provide a Union type, as follows:
type SantasFavoriteCookies = "ginger-bread" | "chocolate-chip";
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;
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;
};
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 };
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 toPresentDeliveryListwould 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];
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>;
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] };
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 aSymbolwhich 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;
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] };
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;
When
T = 'a', that condition will result inF = 'a'andS = ''.
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;
The code is pretty self-explanatory in this one.
Top comments (0)