🔷 TypeScript Q10 / 101

What is generics in TypeScript?

AI-Powered Answer ✓ Answered

Generics in TypeScript are a tool for creating reusable components that can work with a variety of types rather than a single one. They allow you to write flexible, type-safe code without sacrificing type information or requiring `any`.

What are Generics?

Generics provide a way to pass types as parameters to other types, functions, classes, or interfaces. This concept is similar to how function parameters work for values; instead of operating on a specific type, a generic component can operate on a type variable, making it adaptable to different types while maintaining type safety.

Why Use Generics?

The primary reasons to use generics are:

  • Reusability: Write a single component that works with different types, avoiding code duplication.
  • Type Safety: Capture the type of arguments and ensure the return type matches, providing strong type checking at compile time.
  • Flexibility: Adapt components to various data structures or scenarios without losing type information.

Example: The Identity Function

A common example to illustrate generics is the "identity function," which returns whatever is passed into it.

Without Generics (Problematic)

typescript
function identity(arg: any): any {
  return arg;
}

While this identity function can accept any type, using any causes it to lose information about the type that was actually passed in. If you pass a number, TypeScript forgets that it was a number and only knows it's any.

With Generics (Solution)

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

// Usage:
let output1 = identity<string>("myString"); // type of output1 is string
let output2 = identity(123);           // type of output2 is number (type inference)

Here, <T> introduces a type variable T. T acts as a placeholder for the actual type that will be provided when the identity function is called. When we call identity("myString"), T becomes string, and TypeScript knows that output1 is a string.

Common Use Cases for Generics

  • Functions: For creating functions that operate on values of any type, like identity or functions that map/filter arrays.
  • Interfaces: For defining data structures that can hold various types, e.g., a Box<T> interface.
  • Classes: For building reusable data structures like Stack<T>, Queue<T>, or LinkedList<T>.
  • Type Aliases: For creating flexible type definitions.
  • React Components: To define props or state that are generic.

Type Parameters

The type variable, like T in <T>, is called a type parameter. You can use any valid identifier for type parameters, but T (for Type), U, V, K (for Key), E (for Element) are common conventions. Multiple type parameters can be used, e.g., function merge<T, U>(obj1: T, obj2: U): T & U.

Generic Constraints

Sometimes you want to operate on a type T but also need to ensure that T has certain properties. You can restrict the types that can be used with a generic by adding a constraint. This is done using the extends keyword.

typescript
interface Lengthwise {
  length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length); // Now 'arg' is guaranteed to have a .length property
  return arg;
}

// Usage:
loggingIdentity({ length: 10, value: "hello" }); // OK
// loggingIdentity(3); // Error: Argument of type 'number' is not assignable to parameter of type 'Lengthwise'.

Key Takeaways

  • Generics allow you to write components that work with any data type, providing flexibility.
  • They maintain type safety, ensuring that type information isn't lost during operations.
  • Type parameters (e.g., T) are placeholders for actual types.
  • Constraints (e.g., T extends SomeType) allow you to restrict the types that can be used with a generic, ensuring they have certain properties.