Typescript Generics

TypeScript generics offer a powerful mechanism for creating reusable components that can work with various data types. This chapter delves into the world of generics, exploring their core concepts, different use cases, and how they help you write flexible and type-safe code in your TypeScript applications.

Fundamentals of Generics

Type Parameters as Placeholders

Generics introduce type parameters, which act as placeholders for specific types within a function, class, or interface definition. These placeholders can be used throughout the definition, allowing the component to work with different data types without code duplication.

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

const numberIdentity = identity(42);  // Type of numberIdentity is inferred as number
console.log(numberIdentity); // Output: 42

const stringIdentity = identity("hello");  // Type of stringIdentity is inferred as string
console.log(stringIdentity); // Output: "hello"

const booleanIdentity = identity<boolean>(true);  // Type of booleanIdentity is explicitly set as boolean
console.log(booleanIdentity); // Output: true

const arrayIdentity = identity<number[]>([1, 2, 3]);  // Type of arrayIdentity is explicitly set as number[]
console.log(arrayIdentity); // Output: [1, 2, 3]

				
			

Using Angle Brackets ( < > )

Angle brackets (< >) are used to define the type parameter(s) within a generic definition.

				
					function compare<T>(value1: T, value2: T): boolean {
  return value1 === value2;
}

console.log(compare(10, 20));   // Output: false
console.log(compare("hello", "hello")); // Output: true

				
			

Constraining Type Parameters

Using Extends Clause

You can constrain the type parameter to extend a specific interface or class, ensuring the parameter can only be used with types that inherit from that base type.

				
					interface Comparable {
  compareTo(other: this): boolean;
}

function compareWithConstraint<T extends Comparable>(value1: T, value2: T): boolean {
  return value1.compareTo(value2);
}

// This works as User implements Comparable
class User implements Comparable {
  name: string;
  age: number;

  compareTo(other: User): boolean {
    return this.name === other.name && this.age === other.age;
  }
}

const user1 = new User();
user1.name = "Alice";
user1.age = 30;

const user2 = new User();
user2.name = "Alice";
user2.age = 30;

console.log(compareWithConstraint(user1, user2)); // Output: true (assuming compareTo logic works)

				
			

Multiple Type Parameter Constraints

You can define multiple constraints separated by commas for a type parameter.

				
					type StringOrNumber = string | number;

function addOrConcat<T extends StringOrNumber>(value1: T, value2: T): T {
  if (typeof value1 === "string" && typeof value2 === "string") {
    return value1 + value2;
  } else if (typeof value1 === "number" && typeof value2 === "number") {
    return value1 + value2;
  } else {
    // Handle potential type mismatch if constraints are not met
    throw new Error("Incompatible types for addition or concatenation");
  }
}

console.log(addOrConcat("hello", " world")); // Output: "hello world"
console.log(addOrConcat(10, 20));             // Output: 30

				
			

Generic Classes and Interfaces

Defining Generic Classes

You can create generic classes with type parameters to represent data structures that can hold various types of data.

				
					class ArrayContainer<T> {
  private items: T[] = [];

  addItem(item: T): void {
    this.items.push(item);
  }

  getItem(index: number): T {
    return this.items[index];
  }
}

const numberContainer = new ArrayContainer<number>();
numberContainer.addItem(42);
console.log(numberContainer.getItem(0)); // Output: 42

const stringContainer = new ArrayContainer<string>();
stringContainer.addItem("hello");
console.log(stringContainer.getItem(0)); // Output: "hello"

				
			

Generic Interfaces

Similar to classes, you can define generic interfaces to specify the structure of objects that can have different property types.

				
					interface KeyValuePair<K, V> {
  key: K;
  value: V;
}

const nameAgePair: KeyValuePair<string, number> = { key: "Alice", value: 30 };
console.log(nameAgePair); // Output: { key: "Alice", value: 30 }

const productCodePair: KeyValuePair<string, string> = { key: "SKU123", value: "T-Shirt" };
console.log(productCodePair); // Output: { key: "SKU123", value: "T-Shirt" }

				
			

Advanced Generics

Generic Functions with Multiple Type Parameters

Functions can have multiple type parameters for even greater flexibility.

				
					function swap<T, U>(value1: T, value2: U): [U, T] {
  return [value2, value1];
}

const swappedNumbers = swap(10, 20);
console.log(swappedNumbers); // Output: [20, 10] (type of each element inferred)

const swappedStringNumber = swap("hello", 42);
console.log(swappedStringNumber); // Output: [42, "hello"] (type of each element inferred)

				
			

Generic Constraints with Interfaces

You can use interfaces to define constraints for type parameters, promoting code reusability and type safety.

				
					interface Lengthy {
  length: number;
}

function getLength<T extends Lengthy>(value: T): number {
  return value.length;
}

const textLength = getLength("hello world"); // Works as string has length property
console.log(textLength); // Output: 11

// This would cause a compile-time error as User does not have a length property
class User {
  name: string;
  age: number;
}

// const userLength = getLength(new User()); // Error: User does not implement Lengthy

				
			

When to Use Generics

Reusable Components for Different Data Types

Use generics when you need to create functions, classes, or interfaces that can operate on various data types without code duplication.

Improved Type Safety and Readability

Generics enhance type safety by explicitly defining the type relationships within your code. This improves code readability and maintainability.

Best Practices for Generics

  • Clear and Descriptive Type Parameters: Use meaningful names for your type parameters to enhance code clarity.
  • Constraining Type Parameters: Consider using constraints (extends clause) to limit the types that can be used with a generic component.
  • Documenting Generic Components: Provide clear documentation for your generic components, explaining their purpose, type parameters, and usage examples.

Generics are a powerful tool in your TypeScript arsenal. By understanding their core concepts, different use cases, and best practices, you can write flexible, type-safe, and reusable code that adapts to various data types. Generics can significantly improve the maintainability and expressiveness of your TypeScript projects. Happy coding !❤️

Table of Contents

Contact here

Copyright © 2025 Diginode

Made with ❤️ in India