TypeScript Generics Explained Simply
Why Generics?
Without generics, you write code like this:
function getFirst(arr: any[]): any {
return arr[0];
}
const num = getFirst([1, 2, 3]); // type is 'any' — we lost information!
We know it returns a number, but TypeScript doesn’t. This is where generics come in.
Your First Generic
function getFirst<T>(arr: T[]): T | undefined {
return arr[0];
}
const num = getFirst([1, 2, 3]); // type is 'number'
const str = getFirst(["a", "b"]); // type is 'string'
The <T> is a type parameter — it captures whatever type the caller provides, and uses it in the return type.
Generic Constraints
Sometimes you want to restrict what types are allowed:
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(item: T): T {
console.log(item.length);
return item;
}
logLength("hello"); // OK, string has .length
logLength([1, 2, 3]); // OK, array has .length
logLength(42); // Error! number doesn't have .length
Generic Interfaces
This is where generics really shine:
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
interface User {
id: number;
name: string;
}
// fetchUser returns ApiResponse<User>
async function fetchUser(id: number): Promise<ApiResponse<User>> {
const res = await fetch(`/api/users/${id}`);
return res.json();
}
Real-World Example: A useFetch Hook
function useFetch<T>(url: string) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then((json: T) => {
setData(json);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, [url]);
return { data, loading, error };
}
// Usage — TypeScript infers User[] automatically
const { data, loading } = useFetch<User[]>("/api/users");
Key Takeaways
<T>is a placeholder for a type that the caller provides- Use constraints (
extends) to limit what types are accepted - Generics compose beautifully —
Promise<ApiResponse<User[]>>is perfectly valid - Don’t overuse generics — if your function only ever works with strings, just use
string
Generics are one of TypeScript’s most powerful features. They let you write flexible, reusable code while keeping full type safety. Start using them today!