A Gentle Look at Generics

One function, many types

10 min read

The problem generics solve

Suppose you want a small helper that returns the first item of any array. With what you know so far, you would need one version per type:

function firstString(items: string[]): string {
  return items[0];
}

function firstNumber(items: number[]): number {
  return items[0];
}

Two functions with identical bodies, and you would need a third for booleans, a fourth for users, and so on. There has to be a better way, and there is.

Type parameters: a placeholder for a type

A generic function takes a type as a kind of parameter, written in angle brackets. By convention the placeholder is called T:

function first<T>(items: T[]): T {
  return items[0];
}

Read it as: for whatever type T turns out to be, first takes an array of T and returns a single T. The magic is that T is filled in automatically at each call site:

const names = ['Ada', 'Grace', 'Gwen'];
const nums = [3, 1, 4];

const firstName = first(names);
const firstNum = first(nums);

Calling first(names) makes T become string, so firstName is a string. Calling first(nums) makes T become number. One function, full type safety for every caller, and you never wrote the types at the call site; inference handled it.

You have already been using generics

Remember Array<string> from the arrays lesson? That is a generic type: Array is the general idea, and string fills in its type parameter. Popular tools are full of these. When you fetch data in modern libraries you will see types like Promise<User>, meaning a promise that will deliver a User. The pattern is always the same: a container type, with angle brackets saying what is inside.

How deep does this go?

Generics can get fancy, with multiple parameters and constraints, but you do not need any of that yet. For now, two skills are plenty:

  • Reading generics: when you see Something<X>, think a Something containing X.
  • Trusting inference: most of the time you call generic functions like normal functions and TypeScript works out the types.

Do not memorize, recognize. The goal of this lesson is that angle brackets never scare you again. When you meet Promise<Product[]> in the wild, you can calmly read it as: a promise that delivers an array of products.

Quick check

Q1 In function first<T>(items: T[]): T, what is T?

Q2 How should you read the type Promise<User>?

Want to save your progress? It's free.

Create a free account