Course outline · 0% complete

0/26 lessons0%

Course overview →

Generic Interfaces and Types

lesson 6-2 · ~10 min · 19/26

Shapes with a type slot

Containers are everywhere in real code: a list of users, a box holding a cached value, a result wrapping either data or an error. The container's structure is identical no matter what sits inside, so writing one interface per content type would be the same duplication problem generics just solved for functions. Interfaces (Unit 3) can take type parameters too:

interface Box<T> {
  value: T;
}

const numberBox: Box<number> = { value: 7 };
const wordBox: Box<string> = { value: "hi" };

One interface, many concrete shapes. You have used this all course without noticing: number[] is sugar for the generic Array<number>, and the Pair, Result, and Response types in real codebases are generic interfaces.

Generic functions and generic interfaces compose naturally:

function unwrap<T>(box: Box<T>): T {
  return box.value;
}

Pass a Box<number> and you get a number back, guaranteed.

Unwrapping a generic container

unwrap keeps the connection between what goes into the box and what comes out of it.

interface Box<T> {
  value: T;
}

const numberBox: Box<number> = { value: 7 };
const wordBox: Box<string> = { value: "hi" };

function unwrap<T>(box: Box<T>): T {
  return box.value;
}

console.log(unwrap(numberBox) * 2);
console.log(unwrap(wordBox).toUpperCase());

Output

14
HI

Arithmetic works on the first result and a string method works on the second, from one function with one body.

Try unwrap(wordBox) * 2 and the compiler rejects it. unwrap(wordBox) has type string, because wordBox is a Box<string>, so multiplication is not available. The type parameter carried the element type through the call rather than flattening it.

number[] and Array<number>

These two are the same type. The bracket syntax [] is shorthand for the generic Array<T>.

Nothing is different between them, not at compile time and not at runtime. You can write either one and mix both in a single file.

The interesting consequence is historical rather than practical: arrays were your first generic type all along, from the very first lesson that annotated number[]. That is also why array methods like map could keep precise element types back in lesson 5-2. The element type was a type parameter, so map had something concrete to reason about.

Pair and swap: two type parameters

Pair<A, B> holds two values of independent types, and swap returns the same pair with the positions exchanged.

interface Pair<A, B> {
  first: A;
  second: B;
}

function swap<A, B>(p: Pair<A, B>): Pair<B, A> {
  return { first: p.second, second: p.first };
}

const entry: Pair<string, number> = { first: "score", second: 42 };
const flipped = swap(entry);

console.log(entry.first + " -> " + entry.second);
console.log(flipped.first + " -> " + flipped.second);

Output

score -> 42
42 -> score

Reading the two parameters

  • Two type parameters are separated by a comma, as <A, B>. Each is filled in independently at the call.
  • The return type Pair<B, A> is what makes the flip visible to the compiler. Because the order is reversed in the annotation, flipped.first is a number and flipped.second is a string.
  • Annotating the return as Pair<A, B> instead would be a compile error, since the object being returned puts a B in the first slot. The type system checks the swap actually swapped.