Functions and generics
TypeScript generics explained simply
TypeScript generics let functions and types work with many values while keeping type safety. Learn syntax, constraints, and when to skip them.
Generics let you write code that works with many types without throwing away type information. You write one function or one type, and TypeScript still knows exactly what you passed in.
Why generics exist
Say you want a function that returns the first item of an array.
Without generics you have to pick one type:
function firstNumber(items: number[]) {
return items[0]
}
That works for numbers. For strings you copy the function and change the annotation. Do that a third time and you start to feel it.
You could use any instead:
function first(items: any[]) {
return items[0]
}
Now one function handles everything, but TypeScript stops helping you. The result is any, so you lose autocomplete and every compile-time check on it.
Generics fix this. You write the function once, and the type follows the argument:
function first<T>(items: T[]) {
return items[0]
}
const n = first([1, 2, 3]) // number
const s = first(['a', 'b']) // string
The T is a type parameter: a placeholder for a type you don’t know yet. TypeScript fills it in at each call site by looking at the argument. Pass numbers and T is number. Pass strings and T is string. You never spell it out.
Generic constraints with extends
Sometimes the generic type must have certain properties, or the body can’t do its job.
You restrict it with extends:
interface HasName {
name: string
}
function greet<T extends HasName>(person: T) {
console.log(`Hi ${person.name}!`)
}
greet({ name: 'Flavio', age: 40 })
T extends HasName means “any type, as long as it has a string name”. Inside the function you can read person.name, and the caller can still pass an object with extra fields like age. This is the same idea from the TypeScript tutorial: a generic can be limited to a class family or an interface.
Generic types and interfaces
Generics are not just for functions. You can type API responses like this:
type ApiResponse<T> = {
data: T
status: number
}
type User = { id: number, name: string }
const res: ApiResponse<User> = {
data: { id: 1, name: 'Flavio' },
status: 200
}
You write ApiResponse once. Swap User for any other type and the data field follows.
Arrays use the same pattern:
const nums: Array<number> = [1, 2, 3]
Array<number> is a generic type. The type inside the angle brackets is the type argument, the concrete type that fills the parameter.
When not to use generics
My advice is to skip generics when a plain type works.
If your function only ever handles strings, type it as string[]. No generic needed.
If you reach for generics on the first version of a helper, you might be over-engineering. Start simple. Add a generic when you actually need the same logic for a second type.
Generics shine in reusable utilities, API wrappers, and libraries. For a one-off function in your app, a concrete type is often enough.
Lesson completed