Generics

Introduction

Generics in TypeScript enable you to write flexible and reusable code by allowing you to define type parameters for functions, classes, and interfaces, ensuring type safety without compromising on functionality.

Why use generics

Consider the following function passThrough which simply takes in a value and returns it:

typescript
	function passThrough(myValue: number | string): number | string {
	  return myValue;
	}

Currently this function takes in a value myValue which is of a type number or string. However, what if we wanted myValue to be of any type.

We could instead use the any type as follows:

typescript
	function passThrough(myValue: any): any {
	  return myValue;
	}

This is not ideal as an any type means that the function is always going to return an any type. If we were to pass in a number, the return type is still going to be an any type, not a number type.

Ideally what we’d want is for our function to accept any kind of type and then maintain that type. This is where generics come into the picture. A generic will allow us to specify that a function can take any kind of type, and then this will be maintained throughout the function.

How generic types work

Generics work by adding Type (or T) with angled brackets, as seen in the example below:

typescript
	function passThrough<Type>(myValue: Type): Type {
	  return myValue;
	}

If we were to call this function with a number type, then our function would return a number. If we were to call this function with an array type, then our function would return an array type.

In the example above, we used the Type keyword, however <T> is more commonly used. For example:

typescript
	function passThrough<T>(myValue: T): T {
	  return myValue;
	}

Let’s now have a look at the above function in action. In the example below, we are calling our passThrough function with a variable that has a number type:

typescript
	function passThrough<Type>(myValue: Type): Type {
	  return myValue;
	}
	
	const myNumber: number = 10;
	
	passThrough(myNumber);

We can see that our return type is set to the number type. In other words, our return type is set to the type of the value we are passing into our function.

Practical example

We look at a more practical example below.

We have a User type that takes in a generic, type User<T>.

We also have an Address type.

When we create a new User, we pass in our Address type.

typescript
	type User<T> = {
	  id: number;
	  name: string;
	  details: T;
	};
	
	type HomeAddress = {
	  street: string;
	  city: string;
	  postalCode: string;
	  isApartment: boolean;
	};
	
	type WorkAddress = {
	  street: string;
	  city: string;
	  postalCode: string;
	  companyName: string;
	};
	
	const userWithHomeAddress: User<HomeAddress> = {
	  id: 1,
	  name: 'Ola',
	  details: {
	    street: '123 Main St',
	    city: 'Oslo',
	    postalCode: '12345',
	    isApartment: true
	  }
	};
	
	const userWithWorkAddress: User<WorkAddress> = {
	  id: 2,
	  name: 'Kari',
	  details: {
	    street: '456 Corporate Blvd',
	    city: 'Busytown',
	    postalCode: '67890',
	    companyName: 'MegaCorp Inc.'
	  }
	};
	
	console.log(userWithHomeAddress.details.isApartment);
	// Logs: true
	console.log(userWithWorkAddress.details.companyName);
	// Logs: "MegaCorp Inc."

With this example, students can see how we can re-use the same User type for different types of address details. Generics allow us to encapsulate and reuse code patterns while still preserving type safety. Instead of creating a completely new type for every combination (e.g., UserWithHomeAddress, UserWithWorkAddress), we can have a single, flexible User type that adjusts based on what we need.


Lesson task

Goal

For the student to be able to demonstrate the use of generics.

Brief

Follow the Level 1 Process as instructed.

NOTE: Lesson tasks do not get submitted on Moodle and are not assessed by tutors. They are mainly there for you to practise what you have learned in the lesson.

Level 1 process

  1. Create a function called getTypeOf.
  2. Give this function a parameter called currentType.
  3. console.log the typeof of the parameter.
  4. Make the type of the parameter and return type of the function a generic.

Additional resources

TypeScript docs: Generics