TypeScript

Generics

Writing a function once without throwing away what it was given.

After this lesson you can

  • Write a function that keeps the caller's type
  • Constrain a type parameter so you can use it
  • Read the common utility types without looking them up

This function works on anything and tells you nothing:

function first(items: any[]): any { return items[0]; }

A type parameter keeps the connection between what went in and what comes out:

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

const n = first([1, 2, 3]);       // number | undefined
const s = first(["a", "b"]);      // string | undefined

T is not a type; it is a slot the caller fills, usually without noticing, because TypeScript infers it from the argument.

Constraints

Inside the function you may only do what every possible T allows, which is almost nothing. extends narrows what a caller may pass, and in return lets you use it:

function longest<T extends { length: number }>(a: T, b: T): T {
  return a.length >= b.length ? a : b;
}

longest("abc", "de");       // fine: strings have length
longest([1, 2], [3]);       // fine: arrays have length

Try it

A function that keeps the caller's typetypescript-5
function groupBy<T, K extends string>(  items: T[],  key: (item: T) => K,): Record<K, T[]> {  const out = {} as Record<K, T[]>;  for (const item of items) {    const k = key(item);    (out[k] ??= []).push(item);  }  return out;} export function run(): Record<string, string[]> {  const people = [    { name: "Nino", country: "GE" },    { name: "Ana", country: "GE" },    { name: "Luka", country: "DE" },  ];  const byCountry = groupBy(people, (p) => p.country);  // byCountry.GE is known to be an array of those objects, not any.  return {    GE: byCountry.GE.map((p) => p.name),    DE: byCountry.DE.map((p) => p.name),  };}
What to look for

The utility types worth knowing

  • Partial<T> — every property optional. A patch object.
  • Required<T> — the opposite.
  • Pick<T, K> / Omit<T, K> — a subset of the properties.
  • Record<K, V> — an object with keys K and values V.
  • ReturnType<F> — what a function type returns.

They are all written in TypeScript itself, using the same features you have just seen. Nothing here is magic.

Try it yourself

2 visible tests · 2 hidden tests

Implement longest<T extends { length: number }>(items: T[]): T, returning the item with the greatest length. Where two items tie, return the first one. Assume the array always has at least one item — keep the parameter generic enough to work for strings and for arrays.

  • longest(["a","abc","ab"])
  • longest(["only"])
Loading editor…

Sign up to check the hidden tests and save your progress. Sign up