TypeScript Generics

 🚀 Why Use Generics in TypeScript?

TypeScript generics allow you to write flexible, reusable, and type-safe code by enabling parameterized types.

Without generics: Functions and classes require explicit types or rely on any, which loses type safety.
 ❌ With any: You lose type inference and risk runtime errors.

💡 Solution? Generics provide compile-time type safety while keeping the code flexible for different types.

📌 In this article, you’ll learn:
 ✅ How generics improve code reusability.
 ✅ How to use generic functions, classes, and interfaces.
 ✅ Best practices for applying generics effectively.

🔍 The Problem: Hardcoded Types Reduce Reusability

❌ Function Without Generics (Type-Specific)

function getFirstStringElement(arr: string[]): string {
return arr[0];
}

console.log(getFirstStringElement(["apple", "banana"])); // ✅ Works
console.log(getFirstStringElement([1, 2, 3])); // ❌ Error: Argument of type 'number[]' is not assignable to parameter of type 'string[]'

📌 Problems:
 ❌ Only works with string[] – Requires separate functions for number[], boolean[], etc.
 ❌ Not reusable – You must write duplicate code for different types.

🚀 Solution? Use Generics!

✅ Solution: Using Generics for Flexibility

✔ Generic Function to Accept Any Type

function getFirstElement<T>(arr: T[]): T {
return arr[0];
}

console.log(getFirstElement(["apple", "banana"])); // ✅ "apple"
console.log(getFirstElement([1, 2, 3])); // ✅ 1
console.log(getFirstElement([true, false, true])); // ✅ true

📌 Why is this better?
 ✅ Reusable for any type — T is dynamically inferred at compile time.
 ✅ Type-safe – Prevents passing invalid types.
 ✅ Eliminates duplicate functions – Works for string[], number[], boolean[], etc.

🚀 Use generics to make functions work with multiple types while maintaining type safety!

1️⃣ Generic Functions: Making Code More Reusable

✔ Using Generics with Multiple Parameters

function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}

console.log(pair("John", 25)); // ✅ ["John", 25]
console.log(pair(true, { name: "Amit" })); // ✅ [true, { name: "Amit" }]

📌 Why use multiple generic types?
 ✅ Works with mixed types (string, number, boolean, object, etc.).
 ✅ Flexible and type-safe at the same time.

🚀 Use <T, U> for functions handling multiple types!

2️⃣ Generic Interfaces: Creating Flexible Object Structures

✔ Defining a Generic Interface

interface Box<T> {
content: T;
}

let stringBox: Box<string> = { content: "Hello" }; // ✅ Works with string
let numberBox: Box<number> = { content: 42 }; // ✅ Works with number

📌 Why use generic interfaces?
 ✅ Ensures consistent structure while supporting different types.
 ✅ Provides better type inference for objects.

🚀 Use generic interfaces to create flexible and reusable object structures!

3️⃣ Generic Classes: Type-Safe Object-Oriented Programming

✔ Creating a Generic Class

class Storage<T> {
private data: T[] = [];

add(item: T) {
this.data.push(item);
}

getAll(): T[] {
return this.data;
}
}

let stringStorage = new Storage<string>();
stringStorage.add("Apple");
stringStorage.add("Banana");
console.log(stringStorage.getAll()); // ✅ ["Apple", "Banana"]

let numberStorage = new Storage<number>();
numberStorage.add(100);
numberStorage.add(200);
console.log(numberStorage.getAll()); // ✅ [100, 200]

📌 Why use generic classes?
 ✅ Reusable for different types — No need for multiple implementations.
 ✅ Type-safe — Prevents adding wrong data types.

🚀 Use generic classes to manage collections and storage efficiently!

4️⃣ Generic Constraints: Restricting Accepted Types

✔ Limiting Generics to Specific Types

interface HasLength {
length: number;
}

function logLength<T extends HasLength>(item: T): void {
console.log("Length:", item.length);
}

logLength("Hello"); // ✅ Works (string has a length)
logLength([1, 2, 3]); // ✅ Works (array has a length)
logLength(42); // ❌ Error: 'number' does not have a 'length' property

📌 Why use constraints?
 ✅ Prevents invalid type assignments.
 ✅ Ensures generics meet required structure.

🚀 Use <T extends Type> to add constraints on generic types!

5️⃣ Generic Utility Types: Built-in TypeScript Features

✔ Using Partial<T> to Make All Properties Optional

interface User {
id: number;
name: string;
email: string;
}

function updateUser(id: number, updates: Partial<User>) {
console.log(`Updating user ${id} with`, updates);
}

updateUser(1, { name: "Amit" }); // ✅ Only updates 'name'
updateUser(2, { email: "amit@example.com" }); // ✅ Only updates 'email'

📌 Why use Partial<T>?
 ✅ Allows updating objects without requiring all fields.

✔ Using Readonly<T> to Prevent Modifications

interface Product {
id: number;
name: string;
}

const product: Readonly<Product> = { id: 1, name: "Laptop" };

product.name = "Tablet"; // ❌ Error: Cannot assign to 'name' because it is a read-only property.

📌 Why use Readonly<T>?
 ✅ Prevents accidental modifications.

🚀 Use Partial<T> and Readonly<T> for safer object handling!

🔥 Best Practices for Using Generics in TypeScript

Best Practices for Using Generics in TypeScript

🔑 Key Takeaways

Generics allow you to write reusable, type-safe code.
 ✅ Use <T> to create generic functions, interfaces, and classes.
 ✅ Use constraints (extends) to restrict generic types.
 ✅ Use built-in TypeScript utility types like Partial<T> and Readonly<T>.
 ✅ Avoid any – Use generics instead for better type inference.

By mastering TypeScript generics, your code will be more reusable, scalable, and safer! 🚀

📢 Share this article to help developers write better TypeScript!

Comments

Spring Boot 3 Paid Course Published for Free
on my Java Guides YouTube Channel

Subscribe to my YouTube Channel (165K+ subscribers):
Java Guides Channel

Top 10 My Udemy Courses with Huge Discount:
Udemy Courses - Ramesh Fadatare