Generics

August 15, 20254 min read
Requirements:
FunctionsArraysObjects

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

  1. Overusing any – Replace it with generics when you want flexibility plus safety.
  2. Not using generics when needed – If you are writing duplicate functions that differ only by type, stop and introduce <T>.
  3. Forgetting to declare the type parameter – Always add <T> in the definition if you use T inside.
  4. Assuming too much about T – If you need certain properties (like .length), add a constraint with extends.
  5. 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.

Share this article

Stay Updated

Get the latest TypeScript tips, tutorials, and course updates delivered straight to your inbox.

No spam, unsubscribe at any time.