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")
setsT
asstring
, so the function accepts and returns a string.identity<number>(123)
setsT
asnumber
, 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 whereT
can be any type.Box<string>
means the value must be astring
.Box<number>
means the value must be anumber
.
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
becomesstring
, 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
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>
, orResponse<T>
. - Writing APIs or utility functions where input/output types can change.
Also Read: