- TypeScript Tutorial
- TypeScript Home
- TypeScript Introduction
- TypeScript Setup
- TypeScript First Program
- TypeScript vs JavaScript
- TypeScript Data Types
- TypeScript Type Inference
- TypeScript Type Annotations
- TypeScript Interfaces
- TypeScript Enums
- TypeScript Type Aliases
- TypeScript Type Assertions
- TypeScript Variables
- TypeScript Functions
- TypeScript Functions
- TypeScript Optional Parameters
- TypeScript Default Parameters
- TypeScript Rest Parameters
- TypeScript Arrow Functions
- Classes and Objects
- Introduction to Classes
- Properties and Methods
- Access Modifiers
- Static Members
- Inheritance
- Abstract Classes
- Interfaces vs Classes
- Advanced Types
- TypeScript Union Types
- TypeScript Intersection Types
- TypeScript Literal Types
- TypeScript Nullable Types
- TypeScript Type Guards
- TypeScript Discriminated Unions
- TypeScript Index Signatures
- TypeScript Generics
- Introduction to Generics
- TypeScript Generic Functions
- TypeScript Generic Classes
- TypeScript Generic Constraints
- TypeScript Modules
- Introduction to Modules
- TypeScript Import and Export
- TypeScript Default Exports
- TypeScript Namespace
- Decorators
- Introduction to Decorators
- TypeScript Class Decorators
- TypeScript Method Decorators
- TypeScript Property Decorators
- TypeScript Parameter Decorators
- Configuration
- TypeScript tsconfig.json File
- TypeScript Compiler Options
- TypeScript Strict Mode
- TypeScript Watch Mode
TypeScript Generic Functions
In many programming scenarios, you need a function to work with several different data types. While you could use the any type, you lose the safety and "intelligence" of TypeScript. Generic functions solve this by allowing you to create reusable templates where the specific type is decided at the moment the function is called, rather than when it is defined.
What are Generic Functions?
A generic function is a function that uses a "Type Variable"—a special kind of variable that works on types rather than values. This allows the function to capture the type of the input provided by the user and use that same type to define the output or internal logic. This ensures that if you pass a string in, TypeScript knows exactly that a string is coming out, enabling full autocomplete and error checking.
any when they don't know the type. This defeats the purpose of TypeScript. Use Generics to keep your code flexible while maintaining strict type safety.
Syntax of Generic Functions
The hallmark of a generic function is the angle bracket syntax (<T>) placed immediately before the function's parentheses. The letter T is a conventional placeholder for "Type," though you can name it anything.
function genericFunction<T>(value: T): T {
return value;
}
<T>: This declares the type parameter. It tells TypeScript, "We are going to use a type named T in this function."value: T: This specifies that the inputvaluemust be of typeT.: T: This guarantees that the function will return a value of the same typeT.
T, U, and V are standard for simple generics, don't be afraid to use descriptive names like <UserType> or <ApiResponse> in complex functions to improve readability.
Example of a Generic Function
1. Identity Function
An "identity" function is the simplest possible example: it simply returns whatever was passed into it. Without generics, you'd have to write a separate function for numbers, strings, and objects.
function identity<T>(value: T): T {
return value;
}
const num = identity(5); // T is inferred as 'number'
const str = identity("Hello"); // T is inferred as 'string'
const isTrue = identity(true); // T is inferred as 'boolean'
- When you call
identity(5), TypeScript looks at the argument and automatically "infers" thatTshould benumber. - This allows you to use the returned
numvariable with all the standard number methods (like.toFixed()) without manual casting.
2. Generic Function with Multiple Type Parameters
Real-world logic often involves more than one type. For example, you might want to merge two different objects or swap two different values. You can define multiple type parameters separated by commas.
function swap<T, U>(a: T, b: U): [U, T] {
return [b, a];
}
// result is a tuple of [string, number]
const result = swap(100, "Success");
console.log(result[0].toUpperCase()); // Works because TypeScript knows result[0] is a string
- In this example,
TbecomesnumberandUbecomesstringbased on the arguments provided. - The return type
[U, T]ensures that the order of the types in the returned tuple is strictly enforced.
Using Constraints in Generic Functions
Sometimes you want a function to be generic, but only for types that share a certain characteristic. For example, if you want to access a .length property, you need to ensure the type actually has one. You do this using the extends keyword.
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(item: T): number {
console.log(`Length is: ${item.length}`);
return item.length;
}
logLength("Hello World"); // Valid: strings have a length
logLength([1, 2, 3]); // Valid: arrays have a length
// logLength(10); // Error: numbers do not have a length property
- The constraint
T extends { length: number }acts as a filter. It allows any type as long as it possesses a numeric length property. - This is incredibly useful when working with API responses or database models that share common fields like
idorcreatedAt.
T without a constraint, TypeScript will throw an error because it cannot guarantee that the property exists on every possible type.
Generic Functions with Default Types
Just like standard function arguments can have default values, generic parameters can have default types. This makes your function easier to use because the caller doesn't always have to provide a type.
function createCollection<T = string>(): T[] {
return [] as T[];
}
const stringList = createCollection(); // Defaults to string[]
const numberList = createCollection<number>(); // Overridden to number[]
- Default types are perfect for utility functions where a specific type is used 90% of the time, but you still want to allow flexibility for the other 10%.
Example: Generic Function with a Callback
A very common pattern in modern JavaScript is passing a callback function. Generics ensure that the data passed into that callback remains correctly typed throughout the execution flow.
function apiWrapper<T>(url: string, callback: (data: T) => void): void {
// Simulating a fetch call
const mockData = {} as T;
callback(mockData);
}
interface User {
id: number;
name: string;
}
apiWrapper<User>("/api/user/1", (user) => {
console.log(user.name); // 'user' is automatically typed as 'User'
});
- By defining
apiWrapper<T>, thecallbackautomatically knows that its argumentitemmust be of typeT. - This eliminates the need for manual type assertions inside your callback functions.
Summary
Generic functions are a cornerstone of high-quality TypeScript development. They allow you to write "DRY" (Don't Repeat Yourself) code that is both highly flexible and strictly typed. By mastering generics, you move from simply writing code that works to writing professional-grade libraries and applications.
- Reusable Code: Write logic once and apply it to any data structure.
- Type Safety: Retain full IntelliSense and compile-time checking for different types.
- Constraints: Narrow down generic types to ensure they possess specific properties using
extends. - Default Types: Provide sensible defaults to make your APIs cleaner and easier to use.
As you continue your TypeScript journey, look for patterns in your code where you are duplicating logic for different types—these are the perfect candidates for generic functions.