Generics
Why generics make TypeScript click
You have probably written functions or classes that worked fine until you needed them to handle a slightly different type of data. Suddenly you either duplicate code or loosen type safety. Generics are TypeScript’s way of letting you keep one clean implementation while adapting it to many data types without losing the compiler’s help. Think of generics like a mold that can be filled with different materials but still keeps its shape.
Why not just use any
Using the any
type feels like the easy way out. It accepts anything, so you can pass a string, a number, or an object and the compiler will not complain. The problem is that any
also throws away all type information. That means no IntelliSense help and no warnings if you try to use a method that does not exist on the value. Generics give you the same flexibility but preserve the type, so the compiler knows exactly what you are working with.
Example:
function identityAny(arg: any): any {
return arg
}
identityAny(1) // return type: any
identityAny('hello') // return type: any
// using a generic
function identityGeneric<T>(arg: T): T {
return arg
}
identityGeneric(1) // return type: number
identityGeneric('hello') // return type: string
The second version remembers that if you pass in a number, you will get a number back. This is safer and clearer.
Writing a simple generic function
A common starter example is returning the first element from an array. Without generics you would either use any[]
or duplicate the function for each type. With generics you can write it once:
function getFirstElement<T>(arr: T[]): T | null {
return arr.length > 0 ? arr[0] : null
}
TypeScript can infer T
from the argument, so calling getFirstElement([1, 2, 3])
means T
is number
automatically. If you try to pass a string array while forcing T
to number
, the compiler will stop you.
Generics with interfaces and types
Generics also work for object blueprints. Imagine a Person
interface that can store different kinds of extra data per user:
interface Person<T> {
id: number
name: string
data: T
}
This way, Person<string[]>
might store a list of favorite colors, while Person<{ age: number }>
stores structured stats. You still get full type checking on the data
property.
Common beginner mistakes and how to avoid them
- Overusing any – Replace it with generics when you want flexibility plus safety.
- Not using generics when needed – If you are writing duplicate functions that differ only by type, stop and introduce
<T>
. - Forgetting to declare the type parameter – Always add
<T>
in the definition if you useT
inside. - Assuming too much about T – If you need certain properties (like
.length
), add a constraint withextends
. - Manually specifying wrong types – Let inference work unless you have a good reason to override.
A tiny utility pattern
If you find yourself wrapping values often, a simple generic helpre keeps things DRY:
function wrapInArray<T>(value: T): T[] {
return [value]
}
It works for numbers, strings, or even complex objects, while preserving the exact type of the elements.
The takeaway
Generics in TypeScript are type placeholders that get replaced when you use the function, interface, or class. They keep your code reusable and your types intact. Start small, refactor existing any
usages into generics, and explore constraints once you are comfortable. Over time you will see that generics are not just a TypeScript feature but a mindset for writing adaptable and safe code.
If you want to see them in action, try rewriting a utility in your current project using <T>
instead of any
and notice how the compiler’s feedback improves immediately.
Stay Updated
Get the latest TypeScript tips, tutorials, and course updates delivered straight to your inbox.