Want a faster signal before interviews? Take the Interview Ready Check →

DAY 22 OF 30

Generic constraints

Yesterday, in one sentence

What are generics in TypeScript, and why are they useful?

Interview question

How do you create a generic function in TypeScript?

A type parameterType Parameter can be any type at all, so inside the function you cannot assume anything about the type.

function logName<T>(character: T) {
  console.log(character.name);
  // ❌ Property 'name' does not exist on type 'T'
}

T might be a number for example, and numbers do not have a name property.

A generic constraint limits which types the type parameter is allowed to be. You write it with the extends keyword.

type Hero = { name: string };

function logName<T extends Hero>(character: T) {
  console.log(character.name); // ok, every T has a name
}

logName({ name: "Logan", claws: true }); // ok
logName({ claws: true });
// ❌ Argument is missing the property 'name'

T extends Hero means T can be any type that has a name property of type string, which in practice means an object type. Additional properties like claws do not cause an error, because this is structural typingStructural Typing: having the required properties is enough.

The reason to use a constrained type parameter rather than annotating the parameter as Hero is what happens to the return typeReturn Type.

type Hero = { name: string };

function annotated(character: Hero) {
  return character;
}

const a = annotated({ name: "Logan", claws: true });
// a is of type Hero, claws has been lost

function generic<T extends Hero>(character: T): T {
  return character;
}

const b = generic({ name: "Logan", claws: true });
// b is { name: string; claws: boolean }

The annotation replaces the type with Hero. The type parameter keeps whatever was passed in.

How extends works

extends checks whether one type is the same as another or narrower than it. A narrower type is one that fits inside a wider one.

A triangle narrowest at the top. wolverine sits at the narrow top as one specific string, string in the middle as every string, and unknown at the wide base as every type. A narrower type extends a wider one.

"wolverine" is narrower than string, because string contains every string, including "wolverine". string is narrower than unknown, because unknown contains every type, including string.

type A = "wolverine" extends string ? true : false; // true
type B = string extends unknown ? true : false; // true
type C = unknown extends "wolverine" ? true : false; // false

Check yourself

Which call fails?
function getLength<T extends { length: number }>(item: T): number {
  return item.length
}
What is a generic constraint, and how do you use one?