TypeScript Generic Constraints: From Basics to Advanced Patterns
*Master TypeScript generics from basic type parameters to advanced constraint patterns used by expert developers in 2026.*
Introduction: Why Generic Constraints Matter
If you've used TypeScript for more than a few months, you've likely encountered generics. They're the backbone of type-safe code in modern TypeScript applications. But most developers only scratch the surface with basic syntax like `<T>`.
Generic constraints? That's where the real power lives.
Constraints let you tell TypeScript exactly what your generic can and cannot be. Want a function that only accepts objects with an `id` property? Constraints are your answer. Need to ensure a type has a specific method before calling it? Constraints make it possible.
I've been writing TypeScript for nearly a decade, and I'll tell you this: understanding constraints changed how I write code. Not just better typing — but fundamentally better architecture.
In this article, I'll take you from "what's a generic?" to "I can build advanced type systems." We'll cover basic constraints, practical patterns, and real-world examples from production code.
Part 1: The Foundation - Understanding Generics
What Are Generics?
Generics are template types that work with multiple data types while maintaining type safety. They let you write one function that works with any type, while TypeScript still knows exactly what type you're using.
```typescript // A basic generic function function identity<T>(value: T): T { return value; }
const str = identity("hello"); // TypeScript knows str is string const num = identity(42); // TypeScript knows num is number ```
What Are Constraints?
Constraints limit which types can be used with a generic. By default, `<T>` accepts anything. Constraint adds rules: "Only types meeting these requirements."
```typescript // Without constraint - accepts ANY type function identity<T>(value: T): T;
// With constraint - only accepts objectsfunction identifyWithId<T extends { id: string }>(value: T): string { return value.id;}```
The difference? The second function guarantees your object has an `id` property. TypeScript enforces this at compile time, preventing runtime errors.
Part 2: Basic Constraint Patterns
The `extends` Keyword
The simplest constraint uses `extends` to specify a base type:
```typescript // Only accept strings function logString<T extends string>(value: T): void { console.log(value); }
// Only accept numbers function double<T extends number>(value: T): number { return value * 2; }
// Only accept objects function getKeys<T extends object>(value: T): (keyof T)[] { return Object.keys(value); } ```
Object Type Constraints
The most common constraint is limiting to objects with specific properties:
```typescript interface HasId { id: string; }
interface HasName { name: string; }
// Function accepting only objects with id function findById<T extends HasId>(items: T[], id: string): T | undefined { return items.find(item => item.id === id); }
// Function accepting objects with both id and name function findByName<T extends HasId & HasName>(items: T[], name: string): T | undefined { return items.find(item => item.name === name); } ```
Array Constraints
Generic arrays often need constraints on their contents:
```typescript // Function that only works with arrays of objects having id function findItem<T extends { id: string }>( items: T[], id: string ): T | undefined { return items.find(item => item.id === id); }