Skip to content
C Codeloom
JavaScript

TypeScript Generics Introduction

An approachable introduction to TypeScript generics — learn type parameters, constraints, defaults, and how to build reusable type-safe functions and components.

·4 min read · By Codeloom
Beginner 10 min read

What you'll learn

  • What generics are and why they matter
  • Writing generic functions
  • Using extends to constrain types
  • Default type parameters
  • Generics in React components

Prerequisites

  • Familiarity with JavaScript basics

Generics are how TypeScript lets you write code that is both reusable and type-safe. Instead of giving up on types and reaching for any, you let the type system parameterize over the shapes you care about. This tutorial introduces generics gently and shows the patterns you will use every day.

The Problem Generics Solve

Imagine an identity function that returns its argument. Without generics, you would have to pick a type — and any code calling it with a different type would either fail to compile or be cast away.

function identity(value: unknown): unknown {
  return value;
}

const n = identity(42); // type is unknown, not number

We have lost the connection between input and output. Generics restore it.

function identity<T>(value: T): T {
  return value;
}

const n = identity(42); // type is number
const s = identity('hi'); // type is string

The T is a type parameter that captures whatever type the caller passes. TypeScript infers it from the argument, so you rarely have to write it explicitly.

Multiple Type Parameters

Generics can have more than one parameter. A pair helper takes two values and returns a tuple of their types.

function pair<A, B>(a: A, b: B): [A, B] {
  return [a, b];
}

const p = pair('id', 42); // type is [string, number]

Type parameters can also depend on each other. A function that picks a property from an object needs to constrain the key to keys of the object.

Constraints with extends

Sometimes you want a generic that only works for certain shapes. Use extends to constrain the type parameter.

function logLength<T extends { length: number }>(value: T): T {
  console.log(value.length);
  return value;
}

logLength('hello');     // ok, strings have length
logLength([1, 2, 3]);   // ok, arrays have length
// logLength(42);       // Error

The classic example is property access. Use keyof to constrain a key to actual keys of an object.

function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { id: 1, name: 'Ada' };
const name = getProp(user, 'name'); // type is string

This is one of the most useful patterns in TypeScript. It lets utility functions stay type-safe across any object shape.

Default Type Parameters

Like default function arguments, type parameters can have defaults. They are convenient when callers usually want the same type but occasionally need to override.

interface ApiResponse<T = unknown> {
  data: T;
  status: number;
}

const r1: ApiResponse = { data: 'anything', status: 200 };
const r2: ApiResponse<{ id: number }> = { data: { id: 1 }, status: 200 };

Defaults reduce boilerplate at call sites without losing flexibility.

Generic Interfaces and Types

You can also parameterize interfaces and type aliases. This is how containers like arrays, promises, and maps express their element types.

interface Box<T> {
  value: T;
}

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

const numberBox: Box<number> = { value: 42 };
const stringPair: Pair<string, string> = { first: 'a', second: 'b' };

Almost every meaningful type in a TypeScript codebase ends up being generic at some point.

Generics in React Components

React components can be generic too. A reusable list component might take an item type and a render prop.

type ListProps<T> = {
  items: T[];
  render: (item: T) => React.ReactNode;
};

function List<T>({ items, render }: ListProps<T>) {
  return <ul>{items.map((item, i) => <li key={i}>{render(item)}</li>)}</ul>;
}

<List items={[1, 2, 3]} render={n => <>{n * 2}</>} />;

TypeScript infers the type parameter from the items prop. If you call the component with strings, the render callback receives strings too. This kind of inference is part of why generics feel magical once you get used to them.

When to Reach for Generics

Use generics when the same logic applies to many shapes. Do not introduce a generic if a concrete type works fine — over-genericized code is hard to read.

A rule of thumb: if you find yourself reaching for any to express “any type, but the same one in two places,” you want a generic instead.

Wrapping Up

Generics let you keep type safety without sacrificing reuse. Start with single-parameter functions like identity, work your way up to constrained generics with keyof, and you will soon be writing libraries that compose beautifully. Generics are the gateway to truly idiomatic TypeScript.