Generic Types in TypeScript

The second third of this holiday 🦌 TypeScript challenge is here. In our previous post, we touched a bit on generics and explored the fundamentals of keyof and typeof.

In this challenge we will dive typer into Generic types and how they allow us to have more flexibility while maintaining type safety. Let's get into it!

Day three - Dec 3rd

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

Challenge: The Gift Wrapper

The challenge

Did you know that there's also monetary inflation at the North Pole? You betcha, there is. And after 200+ years without a pay raise, Santa's elves are beginning to discuss a general strike.

December 3rd is just about the worst time imaginable for such a strike, and Santa's desperate to calm the elves down. If he can just wrap a few presents, maybe the elves will forget that they're being paid well below market rate (don't worry: the North Pole's actually still a Deleware-based startup so therefore it's ok).

There's a GiftWrapper type to help keep the wrapping process organized, but it needs something... it needs some way to be parameterized. What we have so far is nice as a generic (wink wink) starting point... but it needs some way to provide specific values for Present, From, and To at the type layer..

Please help! Otherwise the reindeer might catch wind of this and start a strike of their own in solidarity with the elves!

The code to complete

type GiftWrapper = {
  present: unknown;
  from: unknown;
  to: unknown;
};

The tests

type test_SantaToTrash_actual = GiftWrapper<'Car', 'Santa', 'Trash'>;
type test_SantaToTrash_expected = { present: 'Car', from: 'Santa', to: 'Trash' };
type test_SantaToTrash = Expect<Equal<
  test_SantaToTrash_actual,
  test_SantaToTrash_expected
>>;

The solution

For this challenge, we need to create a type GiftWrapper that takes three different types as parameters (present, from, and to) and returns an object with those properties.

To solve this we need to rely on a Generic Type, a topic we discussed in the last challenge:

type GiftWrapper<Present, From, To> = {
  present: Present;
  from: From;
  to: To;
};

Let's break down the solution:

  • We defined a generic type GiftWrapper takes three types as parameters: Present, From, and To.

  • This type returns an object with three properties:

    • present of type Present.

    • from of type From.

    • to of type To.

Generic Types

Let's refresh our memory a bit about Generic Types. They are a type that accepts one or more types as parameters. It allows you to create reusable and flexible components by defining types that can work with different data types.

Generics provide a way to write functions, classes, and interfaces that can work with different sets of types while maintaining type safety.

Let's look at an example of creating a generic function:

function makeTuple<A, B>(first: A, second: B): [A, B] {
  return [first, second];
};

Now this is how calling that function will look like:

makeTuple<string, number>('code', 120);

This means that the first parameter is of type string and the second parameter is of type number. The function returns the expected result, which is an array ['code', 120].

You can argue that TypeScript could infer the types for the previous example and that the function shouldn't be generic, but Generics start becoming more helpful when the types of the inputs are not that simple:

type Deer { 
  breed: string;
  name: string;
};

type Elf {
  kind: string;
  name: boolean;
};

makeTuple<Deer, Elf>(
  { breed: 'Reindeer', name: 'Dasher' }, 
  { kind: 'Christmas', name: 'Alabaster Snowball' }
);

I hope you enjoyed today’s TypeScript challenge. We are one more day closer to Christmas 🍪🥛.

See you tomorrow in the next one!