TypeScriptReturnType & Parameters

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.

TS
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>
Note
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.

TS
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]
Tip
Because 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;

TS
// 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.

TS
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.

TS
// 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 sync
ConstructorParameters<T>

ConstructorParameters<T> works like Parameters, but for class constructors. It extracts the parameter tuple of a constructor function.

TS
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().

TS
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');
Note
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.

TS
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.

TS
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) => boolean
Success
You now understand ReturnType<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.