Power FeaturesExtra· 40 min read

Generics: Reusable Typed Code

Generics let one function or type work with many types while staying fully type-safe.

What you will learn

  • Understand the problem generics solve
  • Write a generic function with a type parameter
  • See a generic that keeps the exact type

The problem: one function, many types

Say you want a function that returns the first item of any array. Without generics you would either write one per type, or use any and lose all safety:

Using any loses the real type
// The unsafe way — any throws away the type
function firstAny(items: any[]): any {
  return items[0];
}

const n = firstAny([1, 2, 3]);
n.toUpperCase();      // no error... but n is a number! crash at runtime

Note: Output: (Compiles, then CRASHES: n.toUpperCase is not a function.) Because the return type is any, TypeScript cannot warn us. We need it to remember the real type.

The fix: a type parameter

A generic adds a type parameter, written in angle brackets — usually <T> (T for "Type"). It is a placeholder that gets filled in with the real type each time the function is called.

A generic function that preserves the type
function first<T>(items: T[]): T {
  return items[0];
}

const num = first([1, 2, 3]);        // T becomes number
const word = first(['a', 'b', 'c']); // T becomes string

console.log(num + 10);          // number maths, ok
console.log(word.toUpperCase()); // string method, ok

Note: Output: 11 A For numbers, T is number, so num + 10 works. For strings, T is string, so .toUpperCase() works. One function, full type safety for both.

Calling the wrong method is now caught

The generic remembers num is a number
function first<T>(items: T[]): T {
  return items[0];
}

const num = first([1, 2, 3]);   // T is number
num.toUpperCase();              // error — number has no toUpperCase

Note: Output: Error: Property 'toUpperCase' does not exist on type 'number'. Unlike the any version, the generic kept the real type, so the wrong method is caught at compile time.

Generics in types you already use

You have actually seen generics before. Array<string> is a generic — Array filled with string. Promise<number> is a promise that resolves to a number. The angle brackets pass a type in.

  • Array<T> — a list of T.
  • Promise<T> — an async value of type T (async means a value that arrives later, such as the result of a download).
  • Map<K, V> — a map from keys of type K to values of type V.

Tip: Read <T> as "a type I will fill in later". The huge win: write code once, use it with any type, and never lose type checking.

Q. Why use a generic <T> instead of any?

Answer: A generic remembers the actual type passed in, so TypeScript keeps checking it. any would disable checking and let bugs through.

✍️ Practice

  1. Write a generic last<T>(items: T[]): T that returns the last element, and call it with numbers and strings.
  2. Write a generic wrap<T>(value: T): T[] that returns a one-item array.

🏠 Homework

  1. Write a generic identity<T>(value: T): T function, call it three times with different types, and show each output.
Want to learn this with a mentor?

CodingClave runs guided, project-based training (28-day, 45-day & 6-month batches).

Explore Training →