Conditional Types in TypeScript

On the sixth day of our TypeScript journey, we've covered significant ground—nearly a full week of engaging challenges!
As the title hints, today's challenge has to do with conditional types. We are going to learn about neat concepts of TypeScript as well that are needed for today's solution.
Buckle up, there is still a lot of TypeScript to learn until Christmas 🎅.

Day six - Dec 6th

I strongly encourage you to try the first challenge on your own before reading the solution here.

Challenge:

Filtering The Children (part 1)

The challenge

As you may be aware, kids that are naughty get a lump of coal and kids that are nice get a toy. Santa's sorta controlling, honestly, and he likes being able to manipulate the data by filtering out the naughty kids on some days, and filtering out the nice kids on other days.

So, Santa walks over to the (open floorplan) office where the engineering team sits. Although he's just addressing the engineers, the rest of the office is distracted because they can clearly hear him since there are no walls.

[Santa] You know, this job is a great opportunity for you elves, even without high pay! You're gaining valuable experience, which is more important than money!

[elves] grumble grumble ok. cool. do you need something?

[Santa] YES! So glad you asked! I'd like an idea of how many kids have been nice. I want to be able to filter out all the naughty kids sometimes, and filter out the nice kids other times.

[elf manager] ok. that's fine. we'll add a ticket for the next sprint..

[Santa] Oh! No time for story points and JIRA tickets, I'm afraid! I need this done pronto!

[elf manager] we use Linear, but ok sure. we'll drop everything we're doing.

Can you help?

You're an engineer too. Can you help the elf team?

The first parameter of FilterChildrenBy is just a union of all the children's naughty/nice status. The second parameter is the thing we want to exclude from the final resulting type.

Take a look at the tests. There, you'll see some examples.

Note: the engineering team manager triple checked with Santa because this seems like a strange way to keep track of naughty and nice children, but Santa ensured the manager that this is exactly what he wants.

The code to complete

type FilterChildrenBy = unknown;

The tests

import { Expect, Equal } from 'type-testing';

type test_0_actual = FilterChildrenBy<
  'nice' | 'nice' | 'nice',
  'naughty'
>;
type test_0_expected = 'nice';
type test_0 = Expect<Equal<test_0_expected, test_0_actual>>;

type test_2_actual = FilterChildrenBy<
  string | number | (() => void),
  Function
>;
type test_2_expected = string | number;
type test_2 = Expect<Equal<test_2_expected, test_2_actual>>;

The solution

To solve this prompt, we need to define the FilterChildrenBy type to filter out occurrences of a specified type from a union of types. We can achieve this using conditional types:

type FilterChildrenBy<T, U> = T extends U ? never : T;

FilterChildrenBy takes two type parameters, T and U. It checks whether each type in the union T extends the type U. If it does, it replaces that type with never (meaning it's filtered out), otherwise, it keeps the original type.

Now, from the tests, you may notice that duplicated types in the union are deduplicated from the output of FilterChildrenBy , for example, the expected outcome for the first one is just 'nice':

type test_0_actual = FilterChildrenBy<
  'nice' | 'nice' | 'nice',
  'naughty'
>;
type test_0_expected = 'nice';
type test_0 = Expect<Equal<test_0_expected, test_0_actual>>;

The deduplication of the type 'nice' | 'nice' | 'nice' to just 'nice' happens because TypeScript's union types automatically remove duplicate entries. When you define a union type with duplicate entries, TypeScript treats them as a single occurrence.

So, in the case of the test:

type test_0_actual = FilterChildrenBy<'nice' | 'nice' | 'nice', 'naughty'>;

The union type 'nice' | 'nice' | 'nice' is internally treated as 'nice'. The TypeScript compiler automatically simplifies union types by removing duplicate entries.

Finally, we also use the never type for the first time. In TypeScript, never is a type that represents values that never occur. It is often used in the context of functions that don't return or throw exceptions.

Summary of concepts we learned:

  • Conditional types

  • Automaticatic deduplication in union types

  • The never type

I truly hope you found the challenge interesting and the explanation helpful. With each passing day, we're getting closer to the holiday season—Christmas is just around the corner 🌟🎁. See you tomorrow in the next TypeScript lesson!