Mastering Multiple Generics in TypeScript

Table of Content
- Why Use Multiple Generics?
- Example: A Function Using Multiple Generics
- How It Works
- Usage Example
- Adding Constraints with `extends`
- Benefits of Constraints
- Example Usage with Constraints
- Best Practices with Multiple Generics
- 1. Use Descriptive Names
- 2. Leverage Type Inference
- 3. Combine Generics with Utility Types
- 4. Avoid Overuse
- Conclusion
TypeScript generics provide a powerful way to create reusable and type-safe abstractions. While single generics like
<T>
are common and useful, TypeScript allows you to go a step further: you can use multiple generics in functions, type helpers, and other constructs. This article explores how to effectively use multiple generics, complete with examples and best practices.
Why Use Multiple Generics?
Generics allow TypeScript to infer types based on inputs and maintain type safety across your code. By introducing multiple generics, you can:
- Increase flexibility: Handle diverse input types without sacrificing type safety.
- Improve type inference: Allow TypeScript to infer relationships between inputs and outputs.
- Avoid duplication: Write reusable functions that work seamlessly with various types.
Example: A Function Using Multiple Generics
Consider a function that takes two arguments of different types and returns them as properties of an object. Here's how you can implement it with multiple generics:
const returnBothOfWhatIPassIn = <A, B>(a: A, b: B) => {
return { a, b };
};
How It Works
- Generics Declaration: The function declares two generics,
<A, B>
, which can represent any type. - Type Binding: Each parameter (
a
andb
) is bound to a specific generic type (A
andB
, respectively). - Return Type: The returned object has properties
a
andb
typed asA
andB
, ensuring strong type safety.
Usage Example
When calling this function, TypeScript infers the types automatically:
const result = returnBothOfWhatIPassIn("a", 1);
// Hovering over `result` shows:
// const result: { a: string; b: number; }
This ensures the returned object is correctly typed with a: string
and b: number
, maintaining clarity and type safety.
Adding Constraints with extends
To narrow down the acceptable types for the generics, you can use the extends
keyword:
const returnBothOfWhatIPassIn = <A extends string, B>(a: A, b: B) => {
return { a, b };
};
Benefits of Constraints
- Type Narrowing: Ensures
A
is always a string (or a subtype of string). - Stronger Inference: Improves TypeScript's ability to infer literal types.
Example Usage with Constraints
const result = returnBothOfWhatIPassIn("hello", 42);
// const result: { a: "hello"; b: number; }
const invalid = returnBothOfWhatIPassIn(123, 42);
// Error: Argument of type 'number' is not assignable to parameter of type 'string'.
Constraints provide additional safety and prevent unintended usage.
Best Practices with Multiple Generics
1. Use Descriptive Names
While single-letter generics like <A>
and <B>
are fine in small functions, prefer descriptive names for clarity in complex scenarios:
const mergeObjects = <KeyType, ValueType>(key: KeyType, value: ValueType) => {
return { key, value };
};
2. Leverage Type Inference
Avoid over-specifying types unless necessary. Let TypeScript infer types wherever possible:
const result = returnBothOfWhatIPassIn("example", 10); // TypeScript infers the types.
3. Combine Generics with Utility Types
Generics pair well with TypeScript’s utility types to create more expressive constructs:
type Pair<A, B> = { first: A; second: B };
const createPair = <A, B>(first: A, second: B): Pair<A, B> => {
return { first, second };
};
4. Avoid Overuse
While generics are powerful, overusing them can make your code harder to read. Always balance flexibility with simplicity.
Conclusion
Multiple generics unlock a new level of flexibility and type safety in TypeScript. Whether you’re building reusable functions, managing complex types, or improving inference, mastering this feature is essential for advanced TypeScript development.
By understanding how to use, constrain, and combine generics, you can write cleaner, more robust, and maintainable code. Experiment with these techniques in your projects to get the most out of TypeScript’s powerful type system.