🧠 Generics in TypeScript – The Secret Sauce for Reusable Code!

OohnohassaniOohnohassani
6 min read

Let’s be honest for a second: you ever found yourself copy-pasting the same function and tweaking the types just to handle a slightly different case? 😤
Or you created a new interface that’s 90% similar to another, just because one property had a different type?

Yeah… we've all been there 😭 And it gets messy — fast. Your code becomes repetitive, harder to maintain, and easy to break when things scale. 🚨

That’s where Generics step in like a superhero with a clean cape and a type-safe punch. 🦸‍♂️💥

Generics in TypeScript are like placeholders for types. They let you write reusable and flexible code that works with different types without being limited to one specific type.

🧳 Analogy Time: The Magic Box

Imagine a box that can hold anything — toys, books, or clothes. You don’t want to design a new box for every item.
Instead, you create a box that can adapt to hold any type of item. 📦
Generics are like that adaptable box for your code. One solution that works with many types!

💡 Why Generics Are a Game-Changer

Without generics, you'd have to:

  • Create multiple versions of the same function for different types

  • Lose type safety when using flexible any types

  • Write more code and maintain more edge cases

With generics, you get:

✅ Cleaner code
✅ Type safety
✅ Flexibility
✅ Reusability

That's the dream combo. 🛠️ So let’s explore exactly how they work — and how you can use them to level up your TypeScript game.

🔎 What Are Generics, Really?

In TypeScript, Generics are placeholders for types. Instead of hardcoding a specific type, you define a flexible “type variable” — usually represented as <T> — that gets replaced when the code is used.

Let’s look at this small example:

function echo<T>(value: T): T {
  return value;
}

console.log(echo('Hello'));  // Output: Hello
console.log(echo(42));       // Output: 42

In this case, <T> is like saying:

“Hey TypeScript, I don’t care what type comes in — just use that same type throughout this function.”

It could be a string, a number, a boolean, an object — whatever. It's just a placeholder until the real type comes in.

🤯 Why <T>? Can It Be Anything?

Yes! <T> is just a convention. You can use anything: <Type>, <MyType>, <X>, <Item>, but by convention, we stick with <T> (T for Type).

💡 Analogy: T is Like a Label

Think of <T> as a label on a box. You're telling TypeScript:

"I’m not sure what’s inside yet — it could be toys, tools, or tech — but whatever goes in, treat it consistently."

This gives your code the power of flexibility without losing type safety. And that’s the beauty of generics.

Generics in TypeScript can be categorized as: 1. Built-in Generics ⛳ These are the generics that TypeScript provides as part of its standard library. They are used to handle common use cases, making your code type-safe and flexible.

Examples of Built-in Generics:

i. Array<T>

ii. Promise<T>

iii. Set<T>

iv. Map<K, V>

v. ReadonlyArray<T>

vi. Record<K, V>

2. Custom Generics ⛳ These are generics created by developers to handle specific use cases in their code. Custom generics are used in functions, classes, interfaces, and types to make them reusable and type-safe.

Examples of Custom Generics:

i. Generic Type Alias

ii. Generic Interface

iii. Generic Function

iv. Generic Class

⛳ Built-in Generics in TypeScript

TypeScript already gives us a bunch of generics baked into its core. These are known as Built-in Generics, and they help make standard operations smarter and safer.

1. 📦 Array<T>

Instead of string[], you can write Array<string> — they mean the same thing, but the generic form is more powerful when nesting or composing.

const names: Array<string> = ['Hassani', 'Ayaan', 'Fatima'];
console.log(names); // Output: ['Hassani', 'Ayaan', 'Fatima']

2. ⏳ Promise<T>

Generics are essential in async code too. With Promise<T>, you specify what kind of value the promise will eventually return.

const fetchData = (): Promise<string> => {
  return new Promise(resolve => {
    setTimeout(() => resolve('✅ Done!'), 1000);
  });
};

fetchData().then(data => console.log(data)); // Output: ✅ Done!

3. 🧺 Set<T>

A Set ensures all values are unique. You can define what type the Set holds using generics.

const userIds: Set<number> = new Set([101, 102, 103]);
console.log(userIds); // Output: Set { 101, 102, 103 }

4. 🔑 Map<K, V>

Maps hold key-value pairs. You define both the key and value types: <K, V>.

const userRoles: Map<string, string> = new Map();
userRoles.set('admin', 'read-write');
userRoles.set('guest', 'read-only');

console.log(userRoles); // Output: Map { 'admin' => 'read-write', 'guest' => 'read-only' }

5. 🛡️ ReadonlyArray<T>

Immutable arrays — you can't change them!

const readonlyNumbers: ReadonlyArray<number> = [1, 2, 3];
// readonlyNumbers.push(4); ❌ Error
console.log(readonlyNumbers); // Output: [1, 2, 3]

6. 📋 Record<K, V>

Creates a type with keys K and values V.

type UserRole = 'admin' | 'editor';
type Access = 'full' | 'partial';

const permissions: Record<UserRole, Access> = {
  admin: 'full',
  editor: 'partial',
};

console.log(permissions); 
// Output: { admin: 'full', editor: 'partial' }

⛳ Custom Generics in TypeScript

You can also create your own generics when TypeScript's built-in ones aren't enough.

1. 🧾 Generic Type Alias

Alias with placeholders for types.

type Pair<T, U> = {
  first: T;
  second: U;
};

const coordinates: Pair<number, number> = { first: 50, second: 100 };
console.log(coordinates); // Output: { first: 50, second: 100 }

2. ✍️ Generic Function

Functions with a type placeholder.

function identity<T>(value: T): T {
  return value;
}

console.log(identity<number>(99));  // Output: 99
console.log(identity('TS is powerful!'));  // Output: TS is powerful!

3. 📘 Generic Interface

Interfaces with flexible types.

interface ApiResponse<T> {
  status: number;
  data: T;
}

const userResponse: ApiResponse<{ name: string }> = {
  status: 200,
  data: { name: 'Hassani' },
};

console.log(userResponse);
// Output: { status: 200, data: { name: 'Hassani' } }

4. 🧱 Generic Class

Make classes adaptable to different types.

class Box<T> {
  contents: T;
  constructor(value: T) {
    this.contents = value;
  }

  open(): T {
    return this.contents;
  }
}

const gift = new Box<string>('🎁 Surprise!');
console.log(gift.open()); // Output: 🎁 Surprise!

🧠 Summary

Generics help you write flexible and reusable code while staying type-safe. Think of them as the shape-shifters of your TypeScript toolbox. 🧰

They’re not complicated — they’re powerful. Whether you're using built-in ones like Array<T> and Promise<T>, or creating custom generic functions and classes, you'll find yourself writing smarter, cleaner, and safer code.

📚 References & Further Reading

Want more TypeScript magic? 💫 Follow along on Gocoding for tips, tricks, and deep dives into frontend development.

Happy coding! 🚀

1
Subscribe to my newsletter

Read articles from Oohnohassani directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Oohnohassani
Oohnohassani

Hi 👋 I'm a developer with experience in building websites with beautiful designs and user friendly interfaces. I'm experienced in front-end development using languages such as Html5, Css3, TailwindCss, Javascript, Typescript, react.js & redux and NextJs 🚀