Your First Generic Function

Intermediate · 24 min read · ▶ live playground · ✦ checkpoint

Generics are the steepest step in TypeScript — and the most misunderstood, because they're usually introduced as abstract algebra. They're not. A generic is a solution to a concrete, annoying problem you've probably already hit: a function that works for any type, without throwing the type away. This lesson takes the step slowly, and by the end you'll read <T> as calmly as you read a parameter name.

The duplication problem

Write a function that returns the first element of an array. Easy — for strings:

function firstString(arr: string[]): string {
  return arr[0];
}
 
console.log(firstString(["ada", "grace", "linus"]));

→ ada

Now you need it for numbers. Your options are all bad:

  • Duplicate itfirstNumber, firstBoolean, firstUser… one copy per type, forever.
  • Union itfirst(arr: (string | number)[]): string | number — now every caller gets back a "maybe either" they have to narrow, and the union still can't cover every future type.
  • any itfirst(arr: any[]): any — it runs, but the return type is any: autocomplete dies, typos pass, and the checker is fired from the one place you did the most dynamic thing.

Look at what the three failures have in common: the function's logic never cared about the type, but every way of writing the signature forced a choice too early. What we want to say is: "this works for some type — whichever one you call it with — and returns that same type."

Type parameters — a variable for types

That sentence has a syntax. Add angle brackets after the function name, and inside them declare a type parameter — a placeholder that stands for "the type this particular call uses":

function first<T>(arr: T[]): T {
  return arr[0];
}
 
const name = first(["ada", "grace", "linus"]); // T = string → name: string
const year = first([1969, 1981, 1998]);        // T = number → year: number
 
console.log(name.toUpperCase()); // string methods: available
console.log(year.toFixed(0));    // number methods: available

→ ADA / 1969 — one function, and each call site kept its own precise type.

Read the signature out loud — this is a skill worth drilling until it's automatic:

"first, for some type T, takes an array of T and returns a T."

T is a variable that holds a type instead of a value. Regular parameters connect argument values to return values; type parameters connect argument types to return types. That link — "whatever type comes in is what goes out" — is the entire trick, and it's precisely the information that any throws away.

Try it — one function, every type

Run it, then use the > prompt: call pick with your own arrays — strings, booleans, even the objects from the last line — and watch the echo keep each call's type. Then try the break-it line to see the payoff: the checker knows what comes out because it saw what went in.

typescript — playgroundtype-checked
⌘/Ctrl + Enter to run

Where you've already seen this

Generics have been hiding in plain sight all course:

  • string[] is really Array<string>Array is a generic type with T = string.
  • arr.map(…) returns whatever your callback produces: map is a generic methodmap<U>(fn: (item: T) => U): U[]. Read it out loud: "for some type U, takes a function from T to U, returns an array of U." That's why inference tracked number[] → string so precisely back in 2.1.
  • Promise<string> from lesson 1.3's glance — "a promise of a string" — is a generic type parameterized by what it resolves to.

You haven't been avoiding generics; you've been consuming them through inference since your first array. Today you learned to produce them. The rest of Section 7 adds the missing controls — constraining what T is allowed to be (extends), generic interfaces and classes, and the failure modes that make people say "my generics fight me" — but the mental model is already complete: a type parameter is how a function keeps its caller's type.

Checkpoint

Answer all three to mark this lesson complete

+50 XP on completion