Generics allow you to write reusable and type-safe code that can work with different types. Instead of specifying one fixed type, you can create a reusable component that works with many types.
Generics allow a function, class, or interface to work with different data types while maintaining type safety.
function identity<T>(value: T): T {
return value;
}
Here, T represents a type that will be determined
when the function is used.
Generics help you create reusable code without losing type information.
function identity<T>(value: T): T {
return value;
}
const name = identity("Amit");
const age = identity(25);
The same function can work with both strings and numbers.
The type parameter is written inside angle brackets.
function display<T>(
value: T
): T {
return value;
}
T is a type parameter. You can choose another
meaningful name, but T is commonly used.
function identity<T>(
value: T
): T {
return value;
}
const result =
identity<string>("Hello");
console.log(result);
Here, T becomes string.
function identity<T>(
value: T
): T {
return value;
}
const result =
identity<number>(100);
console.log(result);
In this example, T becomes number.
You often do not need to explicitly provide the generic type.
function identity<T>(
value: T
): T {
return value;
}
const name =
identity("Rahul");
const age =
identity(30);
TypeScript can infer the type from the argument.
Generics can be used to create functions that work with arrays of different types.
function getFirst<T>(
items: T[]
): T {
return items[0];
}
const number =
getFirst([10, 20, 30]);
const name =
getFirst(["Amit", "Rahul"]);
function printItems<T>(
items: T[]
): void {
items.forEach(item => {
console.log(item);
});
}
printItems<number>(
[10, 20, 30]
);
printItems<string>(
["HTML", "CSS", "JavaScript"]
);
A generic function can contain more than one parameter.
function pair<T>(
first: T,
second: T
): T[] {
return [first, second];
}
const numbers =
pair(10, 20);
const names =
pair("Amit", "Rahul");
You can use multiple type parameters.
function createPair<T, U>(
first: T,
second: U
) {
return {
first,
second
};
}
const result =
createPair("Amit", 25);
Here, T and U can represent
different types.
function createObject<T>(
value: T
) {
return {
value
};
}
const result =
createObject("TypeScript");
console.log(result.value);
The object preserves the type of the supplied value.
A type alias can also contain a generic type parameter.
type Box<T> = {
value: T;
};
const numberBox: Box<number> = {
value: 100
};
const stringBox: Box<string> = {
value: "Hello"
};
Interfaces can also be made generic.
interface Box<T> {
value: T;
}
const numberBox: Box<number> = {
value: 100
};
const stringBox: Box<string> = {
value: "Hello"
};
Classes can use generic type parameters.
class Box<T> {
value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
}
const numberBox =
new Box<number>(100);
console.log(
numberBox.getValue()
);
class Box<T> {
constructor(
public value: T
) {}
getValue(): T {
return this.value;
}
}
const box =
new Box<string>("Hello");
console.log(
box.getValue()
);
The same class can work with different types.
Sometimes a generic type needs to satisfy a specific
requirement. You can use extends to create
a constraint.
function getLength<T extends { length: number }>(
value: T
): number {
return value.length;
}
console.log(
getLength("Hello")
);
console.log(
getLength([1, 2, 3])
);
A constraint allows the generic function to safely use properties that are guaranteed to exist.
function getLength<
T extends { length: number }
>(value: T): number {
return value.length;
}
TypeScript knows that value has a
length property.
interface HasId {
id: number;
}
function showId<T extends HasId>(
item: T
) {
console.log(item.id);
}
showId({
id: 101,
name: "Amit"
});
The generic type must contain an id property
of type number.
The keyof operator can be used with generics
to work with the keys of an object.
function getProperty<
T,
K extends keyof T
>(
object: T,
key: K
): T[K] {
return object[key];
}
const student = {
name: "Amit",
age: 20
};
const name =
getProperty(student, "name");
const age =
getProperty(student, "age");
function merge<T, U>(
first: T,
second: U
): T & U {
return {
...first,
...second
};
}
const result =
merge(
{ name: "Amit" },
{ age: 20 }
);
console.log(result);
Multiple generic types can be combined to create reusable object operations.
Generics are commonly used with promises to describe the type of data that will be returned.
function getStudent(): Promise<string> {
return Promise.resolve(
"Amit"
);
}
Here, the promise resolves to a string value.
interface ApiResponse<T> {
data: T;
success: boolean;
}
const response:
ApiResponse<string> = {
data: "Student loaded",
success: true
};
The same API response structure can work with different data types.
interface Student {
id: number;
name: string;
}
interface ApiResponse<T> {
data: T;
success: boolean;
}
const response:
ApiResponse<Student> = {
data: {
id: 101,
name: "Amit"
},
success: true
};
console.log(
response.data.name
);
Generics can make React components reusable with different types of data.
type ListProps<T> = {
items: T[];
};
function List<T>(
{ items }: ListProps<T>
) {
return (
<div>
{items.map((item, index) => (
<div key={index}>
{String(item)}
</div>
))}
</div>
);
}
Generics are especially useful when building reusable React Native components that display different types of data.
type ListProps<T> = {
items: T[];
renderItem: (item: T) => string;
};
function DataList<T>(
{ items, renderItem }: ListProps<T>
) {
return items.map(
item => renderItem(item)
);
}
The component can work with different data types while maintaining type safety.
A generic type parameter can have a default type.
interface Response<
T = string
> {
data: T;
}
const response:
Response = {
data: "Hello"
};
const numberResponse:
Response<number> = {
data: 100
};
Generics are useful for creating small reusable utility functions.
function reverseArray<T>(
items: T[]
): T[] {
return [...items].reverse();
}
const numbers =
reverseArray([1, 2, 3]);
const names =
reverseArray([
"Amit",
"Rahul",
"Neha"
]);
console.log(numbers);
console.log(names);
any instead of a useful generic type.interface Student {
id: number;
name: string;
}
interface ApiResponse<T> {
data: T;
success: boolean;
}
function createResponse<T>(
data: T
): ApiResponse<T> {
return {
data,
success: true
};
}
const student: Student = {
id: 101,
name: "Amit"
};
const response =
createResponse(student);
console.log(
response.data.name
);
console.log(
response.success
);
This example uses a generic function and a generic interface to create a reusable and type-safe API response structure.
Generics are one of the most important features of TypeScript for creating reusable and type-safe code.
function identity<T>(
value: T
): T {
return value;
}
const name =
identity("TypeScript");
const age =
identity(25);
Generics can be used with functions, arrays, interfaces, classes, API responses, React components, and React Native components.
T.extends.keyof can be combined with generics.Question: Which symbol is commonly used to define a generic type parameter in TypeScript?