ReturnType & Parameters
ReturnType<T> and Parameters<T> are TypeScript utility types that let you extract type information from function types without re-declaring it. They are essential when working with external functions, writing adapters, building mocks, or wrapping functions generically.
ReturnType<T>
ReturnType<T> extracts the return type of a function type T. If T is () => string, then ReturnType<T> is string.
function greet(name: string): string {
return `Hello, ${name}!`;
}
type GreetReturn = ReturnType<typeof greet>;
// string
function getUser() {
return { id: 1, name: 'Alice', role: 'admin' as const };
}
type User = ReturnType<typeof getUser>;
// { id: number; name: string; role: 'admin' }
// Works with any function type
type R1 = ReturnType<() => number>; // number
type R2 = ReturnType<() => void>; // void
type R3 = ReturnType<() => never>; // never
type R4 = ReturnType<() => Promise<string>>; // Promise<string>ReturnType requires a function **type**, not a function value. Use typeof when working with function values.Parameters<T>
Parameters<T> extracts the parameter types of a function type as a tuple. This lets you capture the full argument list in a single type.
function createUser(name: string, age: number, admin: boolean): void {
console.log(name, age, admin);
}
type CreateUserParams = Parameters<typeof createUser>;
// [name: string, age: number, admin: boolean]
// Access individual parameter types by index
type NameParam = CreateUserParams[0]; // string
type AgeParam = CreateUserParams[1]; // number
type AdminParam = CreateUserParams[2]; // boolean
// Works with function types directly
type P1 = Parameters<(x: number, y: number) => number>; // [x: number, y: number]
type P2 = Parameters<() => void>; // []
type P3 = Parameters<(msg: string) => void>; // [msg: string]Parameters returns a tuple, you can spread it with ... when forwarding arguments.How They Are Defined
Both utility types use the infer keyword inside conditional types to extract the relevant parts of a function type.
type ReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : any;
type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any ? P : never;
// How ReturnType works manually:
type MyReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : never;
// How Parameters works manually:
type MyParameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any ? P : never;
function add(a: number, b: number): number {
return a + b;
}
type AddReturn = MyReturnType<typeof add>; // number
type AddParams = MyParameters<typeof add>; // [a: number, b: number]Practical: Adapters and Wrappers
A common pattern is wrapping a function with logging, error handling, or timing — while preserving the original function's exact types.
function withLogging<T extends (...args: any[]) => any>(fn: T): T {
return function (...args: Parameters<T>): ReturnType<T> {
console.log('Calling with:', args);
const result = fn(...args);
console.log('Result:', result);
return result;
} as T;
}
function multiply(a: number, b: number): number {
return a * b;
}
const loggedMultiply = withLogging(multiply);
// loggedMultiply has the same type as multiply: (a: number, b: number) => number
loggedMultiply(3, 4); // logs args and result, returns 12
function formatDate(date: Date, locale: string): string {
return date.toLocaleDateString(locale);
}
const loggedFormat = withLogging(formatDate);
// (date: Date, locale: string) => string
loggedFormat(new Date(), 'en-US');Practical: Building Mock Functions
In tests, you often need to mock a function but ensure the mock has the same signature as the real function.
// Suppose this is your real service
function fetchUserById(id: number): Promise<{ id: number; name: string }> {
return fetch(`/api/users/${id}`).then(r => r.json());
}
// Derive a mock type from the real function
type FetchUserById = typeof fetchUserById;
type FetchUserParams = Parameters<FetchUserById>; // [id: number]
type FetchUserReturn = ReturnType<FetchUserById>; // Promise<{ id: number; name: string }>
// Mock that is automatically typed correctly
const mockFetchUser: FetchUserById = async (id: FetchUserParams[0]): FetchUserReturn => {
return { id, name: 'Mock User' };
};
// If the real function's signature changes, the mock type errors immediately
// — no need to manually keep mock types in syncConstructorParameters<T>
ConstructorParameters<T> works like Parameters, but for class constructors. It extracts the parameter tuple of a constructor function.
class Connection {
constructor(
public host: string,
public port: number,
public ssl: boolean
) {}
}
type ConnArgs = ConstructorParameters<typeof Connection>;
// [host: string, port: number, ssl: boolean]
// Use it to create a factory
function createConnection(...args: ConstructorParameters<typeof Connection>): Connection {
return new Connection(...args);
}
const conn = createConnection('db.example.com', 5432, true);
console.log(conn.host); // 'db.example.com'
// Works with built-in classes
type DateArgs = ConstructorParameters<typeof Date>;
// [value?: string | number | Date | ...]InstanceType<T>
InstanceType<T> extracts the instance type of a constructor function — the type you get when you call new T().
class Logger {
log(msg: string) {
console.log(`[LOG] ${msg}`);
}
}
class FileLogger extends Logger {
logToFile(msg: string) {
console.log(`[FILE] ${msg}`);
}
}
type LoggerInstance = InstanceType<typeof Logger>; // Logger
type FileLoggerInstance = InstanceType<typeof FileLogger>; // FileLogger
// Useful when working with generic factory patterns
function createInstance<T extends new (...args: any[]) => any>(
Ctor: T,
...args: ConstructorParameters<T>
): InstanceType<T> {
return new Ctor(...args);
}
const logger = createInstance(Logger);
const fileLogger = createInstance(FileLogger);
logger.log('Hello');
fileLogger.logToFile('Written to file');InstanceType<T> requires a constructor type — use typeof ClassName, not the class name directly.Summary of Function Utility Types
Utility Type | Input | Output |
|---|---|---|
ReturnType<T> | Function type | Return type of the function |
Parameters<T> | Function type | Tuple of parameter types |
ConstructorParameters<T> | Constructor type | Tuple of constructor parameters |
InstanceType<T> | Constructor type | Instance type (result of new T()) |
Combining ReturnType with Awaited
When working with async functions, ReturnType gives you Promise<T>. Combine it with Awaited to unwrap the resolved value type.
async function loadConfig(): Promise<{ apiKey: string; env: string }> {
return { apiKey: 'abc123', env: 'production' };
}
type Raw = ReturnType<typeof loadConfig>;
// Promise<{ apiKey: string; env: string }>
type Resolved = Awaited<ReturnType<typeof loadConfig>>;
// { apiKey: string; env: string }
// Utility to extract resolved return type of any async function
type AsyncReturnType<T extends (...args: any[]) => Promise<any>> =
Awaited<ReturnType<T>>;
type ConfigShape = AsyncReturnType<typeof loadConfig>;
// { apiKey: string; env: string }Forwarding Arguments Safely
Using Parameters to spread arguments into another function ensures you never misalign argument types when forwarding calls.
function sendEmail(to: string, subject: string, body: string): boolean {
console.log(`Sending to ${to}: ${subject}`);
return true;
}
// A wrapper that adds retry logic — fully typed via Parameters/ReturnType
function withRetry<T extends (...args: any[]) => any>(
fn: T,
retries: number
): (...args: Parameters<T>) => ReturnType<T> {
return (...args: Parameters<T>): ReturnType<T> => {
let attempt = 0;
while (attempt <= retries) {
try {
return fn(...args);
} catch (err) {
attempt++;
if (attempt > retries) throw err;
}
}
throw new Error('Unreachable');
};
}
const retrySend = withRetry(sendEmail, 3);
// retrySend has exact same signature as sendEmail
retrySend('alice@example.com', 'Hello', 'World'); // (to: string, subject: string, body: string) => booleanReturnType<T>, Parameters<T>, ConstructorParameters<T>, and InstanceType<T>. These types let you derive signatures from existing functions automatically, keeping wrappers, mocks, and adapters perfectly in sync with the originals.