If you’ve ever wanted to write reusable TypeScript code that’s both flexible and type-safe, generics are your best friend. Generics allow you to define functions, interfaces, and classes that work with any data type, while still enforcing strict type checking. Imagine writing a utility or hook just once and having TypeScript guarantee it works for strings, numbers, objects, or even complex structures, with no loss of type safety.
Whether you’re building simple libraries or scalable app architectures, understanding generics will help you avoid repetitive code, catch more bugs at compile time, and keep your codebase clean and robust.
What are Generics in TypeScript?
Generics are a powerful TypeScript feature that lets you create reusable, type-safe code without specifying concrete types up front. They act as placeholders (like <T>) for types that are filled in later, so your functions, classes, and interfaces can work with any data type and still maintain full type safety.
Example (generic function):
function identity<T>(value: T): T {
return value;
}
const num = identity<number>(42); // num is type number
const str = identity<string>("Hello"); // str is type string
Why Do We Use Generics in TypeScript?
Generics make code more flexible and reusable, while preserving static type checking. Instead of duplicating similar logic for multiple types, you can write one generic function/class/interface that works with them all.
- Prevent runtime type errors by catching mistakes at compile-time
- Write utility code once; reuse for different data structures
- Reduce code duplication
How Generics Improve Type Safety and Reusability
With generics, TypeScript tracks what type is used, “locking in” the type at the usage site but leaving implementation flexible. This lets you catch type mismatches early and build solid, reusable utilities.
Example (interface):
interface Pair<T, K> {
first: T;
second: K;
}
const pair: Pair<string, number> = { first: "one", second: 2 };
When to Use Generics in Functions, Interfaces, and Classes
- Functions: For utilities that operate on any type
- Interfaces/Types: For data structures that hold variable types
- Classes: For things like collections, repositories, or state containers
Example (class):
class Box<T> {
constructor(private value: T) {}
getValue(): T { return this.value; }
}
const numberBox = new Box<number>(42);
Difference Between Generics and any Type
- Use generics for type safety and flexibility; avoid any unless you need to bypass checks deliberately.
Generic React Components and Props (<T> with React.FC)
TypeScript generics help make truly flexible React components:
type ListProps<T> = { items: T[]; renderItem: (item: T) => JSX.Element };
function List<T>({ items, renderItem }: ListProps<T>) {
return <ul>{items.map(renderItem)}</ul>;
}
Creating Reusable Utility Hooks with Generics (e.g., useFetch<T>)
function useFetch<T>(url: string): T | null {
// fetch and parse data as type T
}
const user = useFetch<User>("/api/user"); // infers User type
const posts = useFetch<Post[]>("/api/posts"); // infers Post[]
Implementing Repository Patterns with Generics (Repository<T>)
You can keep your data access code DRY with generic repositories:
class Repository<T> {
private items: T[] = [];
add(item: T) { this.items.push(item); }
findAll(): T[] { return this.items; }
}
const userRepo = new Repository<User>();
const postRepo = new Repository<Post>();
userRepo.add({ id: 1, name: "Alice" });
Debugging Common TypeScript Generic Errors
- Implicit any: TypeScript may infer "any" if the generic type can’t be determined. Always provide explicit types if possible.
- Type constraints: Use "extends" for generic bounds: function logLength<T extends { length: number }>(val: T) {...}
- Mismatched types: Check your assignments carefully; type mismatches show up at compile-time.
How Generics Are Used in Popular Libraries
- React Query: types queries with generics: useQuery<T>()
- Redux Toolkit: infers slice state with generics
- Prisma: returns typed results for any model defined in the schema
- Axios: passes response types with generics: axios.get<T>()
Performance and Maintainability Benefits of Generics
Maintainability: Drastically reduce code duplication. Generic logic is clearer and easier to update. When you change the underlying type or data model, usage updates across the codebase are triggered thanks to the type system.
