Skip to content

Expert · TypeScript basics · topic 45 of 61 · 30 min

Generics

Explanation

Generics are type parameters: placeholders like <T> that let one function or type work with many types while keeping them connected. first<T>(list: T[]): T | undefined returns the same type the list holds. Constraints (T extends …) limit what T can be, and keyof lets you talk about an object's property names.

The problem

Write a function that returns the first item of an array. With any, you lose the type: first([1, 2]) returns any, so first([1, 2]).toUpperCase() is not caught. Writing firstNumber, firstString, firstUser… is silly. Generics let you write it once, with the type as a parameter.

typescript
function first<T>(list: T[]): T | undefined {
  return list[0];
}
const n = first([10, 20]);          // n: number | undefined
const s = first(['tea', 'coffee']); // s: string | undefined
s?.toUpperCase();                   // OK
n?.toUpperCase();                   // Error: Property 'toUpperCase' does not exist on type 'number'.
T is inferred from the argument; you rarely write first<number>(...) yourself.

Generic types

typescript
interface ApiResponse<T> {
  ok: boolean;
  data: T;
  error?: string;
}
type User = { id: number; name: string };
const res: ApiResponse<User[]> = { ok: true, data: [{ id: 1, name: 'Asha' }] };

class Box<T> {
  constructor(private value: T) {}
  get(): T { return this.value; }
}
Generic types describe containers: responses, boxes, lists, promises (Promise<T>).

Constraints: T extends …

Sometimes T must have certain properties. A constraint says "T can be anything, as long as it has these". Inside the function you can then use those properties.

typescript
function byId<T extends { id: number }>(items: T[], id: number): T | undefined {
  return items.find((item) => item.id === id);
}
const u = byId([{ id: 1, name: 'Asha' }], 1); // u keeps its full type
byId(['a', 'b'], 1); // Error: string does not have an id

keyof and indexed access

keyof T is the union of T's property names. T[K] is the type of property K. Together they let you write a fully typed "get a property" helper.

typescript
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
  return items.map((item) => item[key]);
}
const products = [{ name: 'Fan', price: 2499 }, { name: 'Bulb', price: 99 }];
const prices = pluck(products, 'price');  // number[]
pluck(products, 'colour');                // Error: '"colour"' is not assignable to '"name" | "price"'

Default type parameters

typescript
interface Page<T = string> { items: T[]; next?: string }
const tags: Page = { items: ['veg', 'spicy'] }; // T defaults to string
Careful: Do not add generics that do not connect anything. function log<T>(x: T): void is no better than function log(x: unknown): void. A type parameter is useful when it appears at least twice: in an input and an output, or in two inputs that must match.
Think of it like this: A generic is like a tiffin carrier sold "for any food". The carrier is the same design for everyone, but once you pack rajma in it, everyone handling it knows it contains rajma; it does not turn into "some unknown food" on the way.
Tip: You already use generics every day: Array<T>, Promise<T>, Map<K, V>, Set<T>. Reading their definitions in your editor (Ctrl+click) is good practice.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Generics · 1/6

Generics

In this lesson you will write generics, functions and types that work with many types safely.

Prefer a video by a teacher? Find free videos on “typescript generics explained constraints keyof” on YouTube ↗

In real life: A typed API client

A logistics company's front end calls 60 endpoints. One generic helper getJson<T>(url) returns Promise<T>, so each call site states what it expects: getJson<Shipment[]>('/shipments'). The editor then autocompletes shipment fields everywhere and flags typos.

typescript
async function getJson<T>(url: string, fetchFn: typeof fetch = fetch): Promise<T> {
  const res = await fetchFn(url);
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return (await res.json()) as T; // still validate important data at run time
}
interface Shipment { id: string; city: string }
// const list = await getJson<Shipment[]>('/api/shipments'); list[0].city is a string

Practice (0/3 done)

Task 1 · Code · Typescript

Write a generic last<T>(list: T[]): T | undefined that returns the last item.

Task 2 · Code · Typescript

Write byId<T extends { id: number }>(items: T[], id: number): T | undefined.

Task 3 · Code · Typescript

Write groupBy<T, K extends string>(items: T[], keyFn: (item: T) => K): Record<K, T[]> that groups items by the key returned from keyFn, keeping input order.

Quick quiz

Question 1

1.function id<T>(x: T): T { return x; } What is the type of id("hi")?

Question 2

2.What does T extends { length: number } allow?

Question 3

3.type P = { name: string; price: number }. What is keyof P?

Question 4

4.Which built-in type is generic?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

function id<T>(x: T): T { return x; } What is the type of id("hi")?

Answer: string

T is inferred as string from the argument.

What does T extends { length: number } allow?

Answer: Any type that has a numeric length property

Strings, arrays and objects with length all qualify.

type P = { name: string; price: number }. What is keyof P?

Answer: 'name' | 'price'

keyof gives a union of the property names.

Which built-in type is generic?

Answer: Promise<T>

Promise<T> carries the type of its eventual value.

type Product = { id: string; price: number }. What is keyof Product?Related

Answer: "id" | "price"

keyof gives a union of the property names.

From Generics and utility types

Which constraint lets you use a type as a map key?Related

Answer: comparable

Map keys need ==, which comparable guarantees.

From Generics: type parameters and constraints

Which is a generic type in .NET?Related

Answer: Dictionary<TKey,TValue>

Dictionary has two type parameters.

From Generics: reusable, type-safe code

What do generics mainly give you?Related

Answer: Type safety at compile time without casts

Errors move from run time to compile time.

From Generics

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 1 more topic in “TypeScript basics” before its exam: Utility types and real-world TypeScript.
  2. Module 9 exam: TypeScript basics · 12 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Functional patterns and testing. 61 topics left in the course before the final exam.

← Previous: Object types, interfaces and unions