TypeScript Generics

Note: If you're new to TypeScript, check our Getting Started with TypeScript tutorial first.


Generics are a TypeScript feature that let you write functions, classes, or interfaces that can work with any data type, but still preserve type safety.

Instead of using a fixed type like string or number, you use a placeholder (like T), which gets replaced with the actual type at compile time.

For example,

// Generic function that returns the input value
function identity<T>(value: T): T {
  return value;
}

console.log(identity<string>("Hello"));
console.log(identity<number>(123));

Output

Hello
123

The identity<T> function uses a generic type T, which means it can accept any type of input and return the same type as output.

  • identity<string>("Hello") sets T as string, so the function accepts and returns a string.
  • identity<number>(123) sets T as number, so it returns a number.

Generic Interfaces

Generic interfaces allow you to define the shape of data that can work with different data types while still being type-safe.

Like generic functions, they use a type placeholder (like T) that can be set when the interface is used. For example,

// Define a generic interface Box
interface Box<T> {
  value: T; // The value can be of any type specified when using the interface
}

// Create a Box of type string
let stringBox: Box<string>;
stringBox = { value: "Hello" }; // value must be a string
console.log(stringBox.value);  

// Create a Box of type number
let numberBox: Box<number>;
numberBox = { value: 42 }; // value must be a number
console.log(numberBox.value); 

Output

Hello
42

Here,

  • Box<T> is a generic interface where T can be any type.
  • Box<string> means the value must be a string.
  • Box<number> means the value must be a number.

Generics Classes

A generic class allows you to define a class that can work with any data type, while still preserving type safety.

You use a type parameter (like T) that gets replaced with a specific type when the class is used.

class Container<T> {
  private data: T;

  constructor(value: T) {
    this.data = value;
  }

  getData(): T {
    return this.data;
  }
}

// Create a container for a string
const stringContainer = new Container<string>("Programiz");
console.log(stringContainer.getData()); 

// Create a container for a number
const numberContainer = new Container<number>(123);
console.log(numberContainer.getData());

Output

Programiz
123

Here,

  • T is a generic type used to define the type of data the class will store.
  • When you create a new object (new Container<string>("Programiz")), T becomes string, so all properties and methods will expect and return strings.

In the same way, the same class works for any data type, without rewriting it for each type.


Generic Constraints

Generic constraints allow you to limit what types can be used with a generic.

You do this using the extends keyword, which ensures that the type passed must have certain properties or follow a specific structure.

For example,

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

logLength("Hello");       // Works: string has a length
logLength([1, 2, 3]);      // Works: array has a length
// logLength(123);         // Error: number doesn't have length

Output

5
3

T extends {length: number } means: the type T must have a length property (like string, array, etc.).

Now, only values that have a length property (like strings or arrays) can be passed to the function.

Note: Without constraints, generics accept any type — constraints help narrow it down when needed.


Using Generics with Arrays

You can use generics to make functions work with arrays of any type, while keeping type safety.

function getFirstElement<T>(arr: T[]): T {
  return arr[0];
}

console.log(getFirstElement<string>(["a", "b", "c"])); // Output: a
console.log(getFirstElement<number>([10, 20, 30]));    // Output: 10

Here, T[] means an array of type T.

The function getFirstElement<T>(arr: T[]): T is generic, so it works with arrays of any type — like string[], number[], boolean[], or even custom object arrays — while still maintaining type safety.


Frequently Asked Questions

Default Generic Types

Sometimes, you can provide a default type for a generic, which gets used if no type is passed. For example,

interface ApiResponse<T = string> {
  data: T;
  success: boolean;
}

const response1: ApiResponse = { data: "OK", success: true };      // Uses default: string
const response2: ApiResponse<number> = { data: 200, success: true }; // Uses number

When to Use Generics

Use generics when:

  • You want your code to work with multiple data types.
  • You want to avoid repeating the same logic for different types.
  • You want to maintain type safety, so TypeScript can catch errors at compile time.

Examples:

  • Creating a reusable function like identity<T>(value: T): T that works with any type.
  • Building flexible data structures like Box<T>, or Response<T>.
  • Writing APIs or utility functions where input/output types can change.

Also Read:

Did you find this article helpful?

Our premium learning platform, created with over a decade of experience and thousands of feedbacks.

Learn and improve your coding skills like never before.

Try Programiz PRO
  • Interactive Courses
  • Certificates
  • AI Help
  • 2000+ Challenges