TypeScript Generics — Type-Safe Functions and Constraints | TryJS

Learn TypeScript generics with runnable examples. Generic functions, type constraints, default type parameters, and type-safe container patterns.

Overview

Generics let you write functions and types that work with any type while preserving that type information through the call. Instead of falling back to any (which loses type safety), you introduce a type parameter that gets inferred or supplied at the call site. The result: one implementation that's reusable across types, and the compiler still catches mismatches.

How It Works

Type parameters in angle brackets

function first<T>(arr: T[]): T | undefined — T is a placeholder. When you call first([1, 2, 3]), TypeScript infers T = number, so the return type is number | undefined.

Constraints with extends

<T extends { length: number }> means 'any type that has a length property'. The constraint limits which types can be used as T and gives you access to its members inside the function.

Default type parameters

<T = string> provides a fallback when the caller doesn't supply or infer a type. Useful for utility types and optional positions.

Multiple parameters and inference

function map<T, U>(arr: T[], fn: (x: T) => U): U[] — two parameters inferred from the input array and the function's return type. The caller usually never has to supply them manually.

Common Mistakes

When to Use It

Use generics whenever a function or data structure should be reusable across types without losing type information. Collections (Stack<T>, Queue<T>), utility functions (first, last, groupBy), and wrapper types (Result<T>, Maybe<T>) all benefit.

Runnable Example

function first<T>(arr: T[]): T | undefined {
  return arr[0];
}

console.log(first([1, 2, 3]));        // inferred T = number
console.log(first(["a", "b", "c"]));  // inferred T = string

// Generic with constraint
function longest<T extends { length: number }>(a: T, b: T): T {
  return a.length >= b.length ? a : b;
}

console.log(longest("hello", "hi"));
console.log(longest([1, 2, 3], [4, 5]));

// Multiple parameters
function pair<A, B>(a: A, b: B): [A, B] {
  return [a, b];
}
const p = pair("answer", 42); // [string, number]
console.log(p);

// Default type parameter
function wrap<T = string>(value: T): { value: T } {
  return { value };
}
console.log(wrap("hi"));
console.log(wrap<number>(42));

Open this example in the TryJS playground to edit and run the code instantly in your browser — no signup needed.

Frequently Asked Questions

What are generics in TypeScript?

Generics are type parameters that let a function or type work with any type while preserving the specific type at the call site. Instead of returning any, you return T — whatever T was at the call.

What is a generic constraint?

A constraint restricts what types are allowed for a type parameter. <T extends { length: number }> means T must have a length property, which lets you read that property inside the function with type safety.

Do I need to specify generic types when calling a function?

Usually no — TypeScript infers them from the arguments. You only need to specify them when inference can't figure it out, or when you want to override the inferred type.

When should I use a generic instead of a union type?

Use a generic when the caller's specific type should flow through to the return value. Use a union when a fixed set of known types is acceptable and you don't need call-site-specific typing.