JS / TS

TypeScript Generics for Actual Beginners (No Prior Knowledge Assumed)

3 min read

Generics are the point where TypeScript tutorials lose people. The angle brackets look like advanced math, the examples use T as if single letters were self-explanatory, and the payoff is never shown. So let's do this properly: no prior knowledge assumed, one analogy, one before/after example, and proof that you've already been using generics all along.

Why generics feel scary at first

Because they're usually introduced as syntax instead of as a problem being solved. Nobody wakes up wanting "parameterized types." What you actually want is simple: write a function once, use it with any type, and lose no type safety doing it. Generics are just the mechanism for that. Keep the goal in mind and the syntax turns out to be small.

The labeled-box analogy

Imagine a storage service. You hand them anything; they hand you back a box.

A service without labels: you drop in your headphones, get back a box marked "CONTENTS: UNKNOWN." When you open it later, you must inspect the contents yourself — maybe it's headphones, maybe it's someone's soup. That's any: whatever goes in, knowledge of what it was comes out destroyed.

A service with labels: the box comes back marked "CONTENTS: HEADPHONES." Anyone can read the label without opening the box. That label is the generic — a placeholder filled in with the real type at the moment you use it. The service didn't need to know in advance every possible thing people might store; it just promises the label matches the contents.

A function without generics loses type safety

A helper that returns the first item of an array:

function firstItem(items: any[]): any {
  return items[0];
}
 
const names = ['Ava', 'Lin', 'Sam'];
const first = firstItem(names);
 
first.toUpperCase(); // no error now — but no help either
first.toFixed(2); // ALSO no error — this crashes at runtime

The function works, but the type information went into the box and never came out. first is any: the compiler will let you call anything on it, including things that crash. You wrote TypeScript and received JavaScript's guarantees.

The same function with a generic keeps it

function firstItem<T>(items: T[]): T {
  return items[0];
}
 
const names = ['Ava', 'Lin', 'Sam'];
const first = firstItem(names); // T is inferred as string
 
first.toUpperCase(); // ✓ fine — it's a string
first.toFixed(2); // ✗ compile error: toFixed doesn't exist on string

Read the signature as a sentence: "for whatever type T the caller brings, this takes an array of T and returns a T." The label goes on when you call it — and notice you didn't write firstItem<string>(names); TypeScript inferred it from the argument. Most of the time you use generics without writing a single angle bracket.

T is just a variable name, by the way. firstItem<Item>(items: Item[]): Item is identical and arguably clearer. The single letters are convention, not requirement.

You've been using generics all along

  • Array<string> — "an array of strings." The array type is generic; you've filled in its label every time you typed string[].
  • Promise<User> — "a promise that resolves to a User." That's why await gives you a typed value instead of a mystery.
  • Record<string, number>, Partial<User>, Pick<User, 'id'> — the whole utility-types toolbox is generics applied to types.
  • Every typed data-fetching hook — useQuery<Product[]> — is the labeled box again: type in, same type out.

Generics never were an advanced feature bolted onto TypeScript. They're the reason the basic features work — the thing that lets one Array type serve a million element types without collapsing into any. You don't need to master them today; you need to stop being intimidated by them, because from here on the syntax is just labels on boxes.

Found this useful?

Share

I post shorter takes on javascript & typescript and frontend leadership on LinkedIn — follow along there, or get in touch if you're working on something related.