TYPESCRIPT
TypeScript Generics: Syntax, Usage, and Examples
Generics in TypeScript let you create flexible and reusable components that adapt to different types. Instead of hardcoding types, you can define functions, classes, and interfaces that handle multiple data types while maintaining type safety. This makes your code more scalable and prevents unnecessary duplication.
How to Use Generics in TypeScript
To define a generic, use angle brackets (<>
) with a type parameter.
function identity<T>(arg: T): T {
return arg;
}
console.log(identity<string>("Hello")); // Output: Hello
console.log(identity<number>(42)); // Output: 42
Here, T
acts as a placeholder for any type. When calling the function, you can specify the actual type, or TypeScript will infer it for you.
Generic Functions
A generic function adapts to different input types while maintaining type safety.
function getFirst<T>(arr: T[]): T {
return arr[0];
}
console.log(getFirst([10, 20, 30])); // Output: 10
console.log(getFirst(["a", "b", "c"])); // Output: "a"
Since getFirst
uses a generic parameter, you don’t need to write separate functions for different array types.
Generic Interfaces
You can use generics in interfaces to define flexible data structures.
interface Box<T> {
value: T;
}
let numberBox: Box<number> = { value: 123 };
let stringBox: Box<string> = { value: "Hello" };
console.log(numberBox.value); // Output: 123
console.log(stringBox.value); // Output: Hello
With a generic interface, you create structured objects that work with different types.
Generic Classes
You can also use generics in classes to build reusable components.
class Storage<T> {
private data: T[] = [];
addItem(item: T): void {
this.data.push(item);
}
getItems(): T[] {
return this.data;
}
}
const numberStorage = new Storage<number>();
numberStorage.addItem(10);
numberStorage.addItem(20);
console.log(numberStorage.getItems()); // Output: [10, 20]
const stringStorage = new Storage<string>();
stringStorage.addItem("Apple");
console.log(stringStorage.getItems()); // Output: ["Apple"]
This approach keeps your code type-safe while allowing different types of data.
When to Use Generics in TypeScript
Use generics when you need reusable, type-safe functions or data structures. They work well for utility functions like sorting or filtering, where the type may vary. If you're building APIs, generics let you enforce strict type definitions while keeping things flexible.
Generics also help eliminate redundancy. Instead of writing multiple versions of a function for different types, you create one generic implementation that handles them all.
Examples of Generics in TypeScript
Adding Constraints to Generics
Sometimes, you need to ensure a generic type has certain properties. You can use constraints to enforce this.
interface Lengthy {
length: number;
}
function getLength<T extends Lengthy>(item: T): number {
return item.length;
}
console.log(getLength("Hello")); // Output: 5
console.log(getLength([1, 2, 3])); // Output: 3
By extending Lengthy
, this function only accepts types with a length
property.
Generic Arrow Functions
You can also use generics in arrow functions for cleaner syntax.
const reverseArray = <T>(items: T[]): T[] => items.reverse();
console.log(reverseArray([1, 2, 3])); // Output: [3, 2, 1]
console.log(reverseArray(["a", "b", "c"])); // Output: ["c", "b", "a"]
This approach makes generic functions more concise and readable.
Working with Multiple Type Parameters
A function can use multiple generic parameters to handle different types.
function merge<T, U>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 };
}
const person = merge({ name: "Alice" }, { age: 30 });
console.log(person); // Output: { name: "Alice", age: 30 }
Here, merge
takes two objects with different types and merges them into one.
Learn More About TypeScript Generics
Optional Generics
You can set a default type for generics, making them optional.
function wrapValue<T = string>(value: T): { wrapped: T } {
return { wrapped: value };
}
console.log(wrapValue(42)); // Output: { wrapped: 42 }
console.log(wrapValue()); // Output: { wrapped: "" } (default type is string)
This makes your functions more adaptable without requiring explicit type arguments.
Generics with Object Types
Generics let you enforce type safety while working with objects.
function extractProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
const user = { name: "John", age: 28 };
console.log(extractProperty(user, "name")); // Output: John
This function ensures that key
is a valid property of the object.
Generic Arrays
Generics help when filtering or modifying arrays while maintaining consistent types.
function filterItems<T>(arr: T[], predicate: (item: T) => boolean): T[] {
return arr.filter(predicate);
}
console.log(filterItems([1, 2, 3, 4], (n) => n > 2)); // Output: [3, 4]
console.log(filterItems(["apple", "banana"], (s) => s.includes("a"))); // Output: ["banana"]
This makes array operations more flexible without losing type safety.
Advanced Generic Constraints
You can enforce stricter constraints to refine generic behavior.
interface HasId {
id: number;
}
function findById<T extends HasId>(items: T[], id: number): T | undefined {
return items.find((item) => item.id === id);
}
const users = [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }];
console.log(findById(users, 2)); // Output: { id: 2, name: "Bob" }
This function ensures only objects with an id
property can be used.
Checking Generic Types at Runtime
Since TypeScript types disappear at runtime, you can’t directly check a generic type. However, you can enforce constraints.
function isString<T>(value: T): boolean {
return typeof value === "string";
}
console.log(isString("hello")); // Output: true
console.log(isString(100)); // Output: false
This function provides a way to check types without losing generic flexibility.
Sign up or download Mimo from the App Store or Google Play to enhance your programming skills and prepare for a career in tech.