Back to Curriculum

Generics and Type Parameters

📚 Lesson 5 of 15 ⏱️ 45 min

Generics and Type Parameters

45 min

Generics allow you to create reusable components that work with multiple types while maintaining type safety. They enable you to write code that works with any type without losing type information. Generics are one of TypeScript's most powerful features, enabling you to build flexible, reusable, and type-safe code.

They provide type safety while maintaining flexibility. Instead of using `any` (which loses type information) or creating separate functions for each type, generics let you write one function that works with any type while preserving type relationships. The compiler ensures type safety at compile time.

Generic functions, classes, and interfaces can be constrained to specific types using `extends`. Constraints allow you to specify that a type parameter must have certain properties or extend a particular type. This enables you to use those properties safely within the generic code while still maintaining flexibility.

Multiple type parameters can be used together, enabling complex type relationships. For example, `function map<T, U>(arr: T[], fn: (item: T) => U): U[]` maps from type T to type U. Understanding how to use multiple generics is crucial for advanced TypeScript patterns.

Generic constraints with `keyof` and mapped types enable powerful type transformations. You can create utility types that transform object types, extract property types, or create new types based on existing ones. These patterns are used extensively in libraries and frameworks.

Default type parameters allow you to provide default types for generics, making them easier to use when the default is appropriate. This reduces the need to specify types explicitly in common cases while still allowing customization when needed.

Key Concepts

  • Generics create reusable components that work with multiple types.
  • Type parameters (T, U, etc.) represent types to be specified later.
  • Generic constraints (extends) limit what types can be used.
  • Multiple type parameters enable complex type relationships.
  • Generics maintain type safety while providing flexibility.

Learning Objectives

Master

  • Creating generic functions, classes, and interfaces
  • Using type parameters and constraints
  • Working with multiple generic type parameters
  • Understanding generic constraints and keyof operator

Develop

  • Generic programming thinking
  • Understanding type relationships and transformations
  • Designing reusable, type-safe components

Tips

  • Use descriptive names for type parameters (T, U) or meaningful names (TKey, TValue).
  • Add constraints when you need to use properties of the type parameter.
  • Use default type parameters to make generics easier to use.
  • Leverage generics to create reusable utility functions and classes.

Common Pitfalls

  • Over-constraining generics, limiting their usefulness.
  • Not using generics when you should, leading to code duplication.
  • Using any instead of generics, losing type safety.
  • Creating overly complex generic types that are hard to understand.

Summary

  • Generics enable reusable, type-safe code that works with multiple types.
  • Type parameters represent types to be specified when using the generic.
  • Constraints limit what types can be used while maintaining flexibility.
  • Generics are essential for building reusable libraries and components.

Exercise

Create a generic function that can work with arrays of any type.

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

// Usage with different types
const numbers = firstElement([1, 2, 3]);
const strings = firstElement(["hello", "world"]);

console.log(numbers); // 1
console.log(strings); // "hello"

Exercise Tips

  • Add a constraint: function firstElement<T extends { id: number }>(arr: T[]).
  • Create a generic class: class Box<T> { constructor(public value: T) {} }.
  • Use multiple type parameters: function map<T, U>(arr: T[], fn: (item: T) => U): U[].
  • Experiment with generic interfaces: interface Repository<T> { findById(id: number): T }.

Code Editor

Output