| |

TypeScript 36 ๐Ÿ”ท Utility Types โ€” ReturnType, Parameters, Awaited, InstanceType

The previous two chapters covered utilities for objects (Partial, Pick, Omit, Record) and unions (Exclude, Extract, NonNullable). This chapter covers the utilities that work with functions and constructors: ReturnType<T> extracts a function’s return type, Parameters<T> extracts its parameter tuple, Awaited<T> unwraps a promise (recursively), and InstanceType<T> extracts the instance type from a constructor. All four are built on infer โ€” the keyword from Chapter 32 that captures a type inside a conditional pattern. Learn these, and you can type the pieces of any function or class without duplicating them.

Key point: Each of these utilities answers a specific question about a function or constructor. ReturnType โ€” “what does it return?” Parameters โ€” “what does it take?” Awaited โ€” “what’s the resolved value?” InstanceType โ€” “what does new produce?” They’re all infer in a pattern: a function pattern for ReturnType and Parameters, a promise pattern for Awaited, a constructor pattern for InstanceType. Once you can read them, you can write your own โ€” every “extract X from a function” utility is the same shape.


ReturnType<T> โ€” the function’s return type

ReturnType<T> gives the type a function returns.

function greet(name: string): string {
  return `Hello, ${name}`;
}

type GreetReturn = ReturnType<typeof greet>;
// string

typeof greet is the function’s type; ReturnType extracts what it returns.

Implementation:

type ReturnType<T extends (...args: any) => any> =
  T extends (...args: any) => infer R ? R : any;

The conditional matches a function pattern and captures the return type as R.

On arrow functions and methods:

const add = (a: number, b: number) => a + b;
type AddReturn = ReturnType<typeof add>;
// number

class Calculator {
  multiply(a: number, b: number): number { return a * b; }
}

type MultiplyReturn = ReturnType<Calculator['multiply']>;
// number

Calculator['multiply'] is the method type โ€” indexed access on the class instance type.

On async functions: An async function returns a Promise<T>. ReturnType gives the promise, not the resolved value.

async function fetchUser(id: number): Promise<User> {
  const res = await fetch(`/users/${id}`);
  return res.json();
}

type FetchReturn = ReturnType<typeof fetchUser>;
// Promise<User>

type FetchValue = Awaited<ReturnType<typeof fetchUser>>;
// User

Combine ReturnType with Awaited to get the resolved value.

On generic functions:

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

type IdentityReturn = ReturnType<typeof identity>;
// unknown โ€” T is unresolved

Without a concrete type argument, the return is unknown. Use an instantiated version:

type StringIdentity = ReturnType<typeof identity<string>>;
// string

Why ReturnType matters: You often need to type a variable or parameter as “whatever this function returns” without repeating the type. ReturnType derives it. If the function’s return type changes, every consumer updates automatically.

The constraint T extends (...args: any) => any: The type argument must be a function. This prevents misuse and lets the conditional pattern match.

When ReturnType doesn’t work: When T is any, or when T isn’t a function. The result is any or a compile error.

Why “ReturnType” and not “Output”: TypeScript uses the term from the function signature โ€” the return. “Return type” is the standard term in languages with typed functions. Keeping it familiar means less to learn.


Parameters<T> โ€” the function’s parameter tuple

Parameters<T> gives a function’s parameter types as a tuple.

function createUser(name: string, age: number): User {
  return { name, age };
}

type CreateUserParams = Parameters<typeof createUser>;
// [name: string, age: number]

The result is a labeled tuple โ€” position and name preserved.

Implementation:

type Parameters<T extends (...args: any) => any> =
  T extends (...args: infer P) => any ? P : never;

The conditional captures the parameter list as P.

Accessing individual parameters:

type FirstName = Parameters<typeof createUser>[0];
// string

type Age = Parameters<typeof createUser>[1];
// number

Index into the tuple to get one parameter.

Spread and rest:

function log(level: string, ...messages: string[]): void { }

type LogParams = Parameters<typeof log>;
// [level: string, ...messages: string[]]

The tuple includes the rest parameter.

On methods:

class Api {
  request(url: string, method: 'GET' | 'POST'): Promise<Response> {
    // ...
  }
}

type RequestParams = Parameters<Api['request']>;
// [url: string, method: 'GET' | 'POST']

On constructors: Parameters doesn’t work directly on class constructors โ€” use ConstructorParameters<T> for that.

class User {
  constructor(public name: string, public age: number) {}
}

type UserCtorParams = ConstructorParameters<typeof User>;
// [name: string, age: number]

ConstructorParameters<T> is the constructor equivalent of Parameters.

Why Parameters matters: Wrapping or forwarding functions requires knowing their parameter types. Parameters extracts them so wrappers can be typed precisely.

function withLogging<T extends (...args: any[]) => any>(fn: T): T {
  return ((...args: Parameters<T>) => {
    console.log('called with', args);
    return fn(...args);
  }) as T;
}

withLogging forwards arguments to fn โ€” Parameters<T> types them.

On functions with no parameters:

function now(): number { return Date.now(); }

type NowParams = Parameters<typeof now>;
// []

The tuple is empty.

Why the tuple and not an array: Positional parameters are a tuple โ€” each slot has a specific type. Typing them as an array would lose that.

Why “Parameters” is the right name: It’s plural because functions have many parameters. The tuple captures them in order. The name matches the mental model of “what does this function take?”

Why labeled tuples: The parameter names are preserved in the tuple โ€” [name: string, age: number]. That’s for editors and tooling; the types are what matter. Labels make hover tooltips readable.


Awaited<T> โ€” unwrap a promise, recursively

Awaited<T> gives the resolved value of a promise, unwrapping nested promises.

type A = Awaited<Promise<string>>;
// string

type B = Awaited<Promise<Promise<number>>>;
// number

type C = Awaited<string>;
// string (not a promise)

Awaited recurses through nested promises until it reaches a non-promise.

Implementation:

type Awaited<T> =
  T extends null | undefined ? T :
  T extends object & { then(onfulfilled: infer F, ...args: any[]): any }
    ? F extends (value: infer V, ...args: any[]) => any
      ? Awaited<V>
      : never
    : T;

The definition is complex because it matches anything with a then method โ€” not just Promise. TypeScript’s actual implementation handles thenables too.

On async function returns:

async function fetchUser(): Promise<User> {
  return { id: 1, name: 'Alice' };
}

type UserPromise = ReturnType<typeof fetchUser>;
// Promise<User>

type User = Awaited<UserPromise>;
// User

The common pattern: Awaited<ReturnType<T>> gives the resolved value of an async function.

On non-promise values:

type A = Awaited<string>;
// string

If T isn’t a promise, Awaited<T> returns T unchanged. That makes it safe to apply to any type.

On PromiseLike:

interface MyPromise<T> {
  then<R>(onfulfilled: (value: T) => R): MyPromise<R>;
}

type A = Awaited<MyPromise<string>>;
// string

Anything with a then method is treated as thenable.

Why Awaited matters: async functions hide the resolved type behind Promise. Awaited recovers it. It’s how you write “the value this async function returns” without duplicating the type.

In Promise.all and similar:

const promises = [Promise.resolve(1), Promise.resolve('a')] as const;
type Values = Awaited<(typeof promises)[number]>;
// 1 | 'a'

Awaited distributed over the union of promise types gives the union of resolved values.

Why recursion: A promise can resolve to another promise. Promise<Promise<T>> resolves to T after two awaits. Awaited handles arbitrary depth.

Why Awaited was added: Before TypeScript 4.5, unwrapping promises required a custom conditional. Awaited made it standard โ€” and handled thenables, recursion, and edge cases correctly.

Why “Awaited” and not “Unwrap”: It’s the past-tense of “await” โ€” the type you get after awaiting the promise. The name matches the operation: await promise gives the awaited value. The utility gives the awaited type.


InstanceType<T> โ€” the constructor’s instance

InstanceType<T> gives the instance type a constructor produces.

class User {
  name = '';
  greet(): string { return `Hi, ${this.name}`; }
}

type UserInstance = InstanceType<typeof User>;
// User

typeof User is the constructor type; InstanceType extracts what new User() produces.

Implementation:

type InstanceType<T extends abstract new (...args: any) => any> =
  T extends abstract new (...args: any) => infer R ? R : any;

The conditional matches a constructor pattern and captures the instance type.

On abstract classes:

abstract class Animal {
  abstract speak(): string;
}

class Dog extends Animal {
  speak(): string { return 'woof'; }
}

type AnimalInstance = InstanceType<typeof Animal>;
// Animal

InstanceType works on abstract constructors too โ€” the constraint is abstract new.

In factory functions:

function create<T extends new () => object>(Ctor: T): InstanceType<T> {
  return new Ctor();
}

const user = create(User);
// User

InstanceType<T> gives the correct type for the factory’s return.

On built-in constructors:

type DateInstance = InstanceType<typeof Date>;
// Date

type MapInstance = InstanceType<typeof Map>;
// Map<any, any>

For generics, the instance type uses its default type arguments.

Why InstanceType matters: Given a class reference, you often need the instance type โ€” for a factory, a registry, or a decorator. InstanceType derives it from the constructor type.

The distinction from typeof:

type A = typeof User;         // constructor type
type B = InstanceType<typeof User>;  // instance type
type C = User;                // instance type

typeof User and User are different โ€” one is the constructor, the other is what it produces. InstanceType bridges them.

Why “Instance”: The result of new is an instance. The utility gives the instance type โ€” what you get when you call new. The name matches the outcome.

Why not just use User directly: When you have the class as a value (like typeof User), you can’t always name the instance type directly. InstanceType derives it. That’s essential in generics and factories, where the class is a type parameter.


Combining the four

These utilities combine in real patterns.

Wrap a function preserving its signature:

function memoize<T extends (...args: any[]) => any>(fn: T): T {
  const cache = new Map<string, ReturnType<T>>();
  return ((...args: Parameters<T>) => {
    const key = JSON.stringify(args);
    if (!cache.has(key)) {
      cache.set(key, fn(...args));
    }
    return cache.get(key);
  }) as T;
}

Parameters<T> types the wrapper’s parameters; ReturnType<T> types the cached value.

Async wrapper:

type AsyncFn<T> = (...args: Parameters<T>) => Promise<ReturnType<T>>;

function promisify<T extends (...args: any[]) => any>(
  fn: T
): AsyncFn<T> {
  return (...args) => Promise.resolve(fn(...args));
}

Takes a sync function, returns an async version with the same parameters.

Class factory:

function makeFactory<T extends new (...args: any[]) => any>(
  Ctor: T
): (...args: ConstructorParameters<T>) => InstanceType<T> {
  return (...args) => new Ctor(...args);
}

const createUser = makeFactory(User);
const user = createUser('Alice', 30);  // User

Constructor parameters go in, an instance comes out.

Async wrapper with resolved type:

async function fetchData<T>(url: string): Promise<T> {
  const res = await fetch(url);
  return res.json();
}

type FetchedUser = Awaited<ReturnType<typeof fetchData<User>>>;
// User

Chain Awaited and ReturnType to get the resolved value of an async function.

Extracting service method signatures:

class UserService {
  getUser(id: number, includeDeleted: boolean): Promise<User> {
    // ...
  }
}

type GetUserParams = Parameters<UserService['getUser']>;
// [id: number, includeDeleted: boolean]

type GetUserResult = Awaited<ReturnType<UserService['getUser']>>;
// User

The parameters and the resolved result are both derived from the method.

Why these combinations matter: Real code wraps, forwards, and delegates. The utilities extract the pieces so wrappers can be typed without duplicating signatures. Every decorator, middleware, and higher-order function uses them.

Why Parameters and ReturnType pair naturally: They’re complements. Together they describe the full signature โ€” inputs and outputs. Wrappers need both to preserve the shape.


A full example

A typed middleware system using the four utilities.

// ============================================
// BASIC FUNCTION
// ============================================

interface User {
  id: number;
  name: string;
  email: string;
}

class UserRepository {
  async getUser(id: number, includeDeleted: boolean = false): Promise<User> {
    const res = await fetch(`/users/${id}?deleted=${includeDeleted}`);
    return res.json();
  }

  async saveUser(user: User): Promise<void> {
    await fetch('/users', {
      method: 'POST',
      body: JSON.stringify(user)
    });
  }
}

// ============================================
// EXTRACTING SIGNATURES
// ============================================

type GetUserMethod = UserRepository['getUser'];
type GetUserParams = Parameters<GetUserMethod>;
// [id: number, includeDeleted?: boolean]

type GetUserPromise = ReturnType<GetUserMethod>;
// Promise<User>

type GetUserResult = Awaited<GetUserPromise>;
// User

type SaveUserParams = Parameters<UserRepository['saveUser']>;
// [user: User]

// ============================================
// WRAPPING WITH LOGGING
// ============================================

function withLogging<T extends (...args: any[]) => any>(fn: T): T {
  return ((...args: Parameters<T>): ReturnType<T> => {
    console.log(`Calling with ${args.length} args`);
    const result = fn(...args);
    console.log('Call returned');
    return result;
  }) as T;
}

// ============================================
// WRAPPING WITH RETRY
// ============================================

function withRetry<T extends (...args: any[]) => Promise<any>>(
  fn: T,
  attempts = 3
): (...args: Parameters<T>) => ReturnType<T> {
  return async (...args) => {
    let lastError: unknown;
    for (let i = 0; i < attempts; i++) {
      try {
        return await fn(...args);
      } catch (err) {
        lastError = err;
      }
    }
    throw lastError;
  };
}

// ============================================
// CLASS FACTORY
// ============================================

function createRepository<T extends new (...args: any[]) => any>(
  Ctor: T
): (...args: ConstructorParameters<T>) => InstanceType<T> {
  return (...args) => new Ctor(...args);
}

// ============================================
// USAGE
// ============================================

const repo = createRepository(UserRepository);
// repo: () => UserRepository

const loggedRepo = withLogging(repo);
const appRepo = loggedRepo();

// Wrap the method with retry
const retryGetUser = withRetry(appRepo.getUser.bind(appRepo));

async function main(): Promise<void> {
  const user = await retryGetUser(42, false);
  // user: User

  console.log(user.name);
}

// ============================================
// TYPE-LEVEL VERIFICATION
// ============================================

// These would be compile errors if the types were wrong:
type _Check1 = GetUserResult extends User ? true : false;   // true
type _Check2 = GetUserParams extends [number, boolean?] ? true : false;  // true
type _Check3 = InstanceType<typeof UserRepository> extends UserRepository ? true : false;  // true

console.log('Types verified');

What this shows:

  • Parameters<UserRepository['getUser']> โ€” the parameter tuple
  • ReturnType<UserRepository['getUser']> โ€” the promise
  • Awaited<...> โ€” the resolved value
  • InstanceType<typeof UserRepository> โ€” the class instance
  • ConstructorParameters<typeof UserRepository> โ€” the constructor arguments
  • withLogging โ€” wraps any function, preserving its signature via Parameters<T> and ReturnType<T>
  • withRetry โ€” wraps an async function, preserving parameters and return type
  • createRepository โ€” factory with constructor parameters in, instance out

Every wrapper keeps the exact signature. No duplication, no any.

Why this shape: It’s how real middleware works. Wrappers preserve the function they wrap โ€” the same parameters, the same return type. The utilities make that possible at the type level. The compiler checks that a wrapper matches its target.


Complete Example Session

# ============================================
# PART 1: RETURN TYPE
# ============================================

cat > return.ts << 'EOF'
function add(a: number, b: number): number {
  return a + b;
}

type AddReturn = ReturnType<typeof add>;
// number

const result: AddReturn = 42;

// Async function
async function fetchUser(): Promise<{ id: number }> {
  return { id: 1 };
}

type FetchReturn = ReturnType<typeof fetchUser>;
// Promise<{ id: number }>

const p: FetchReturn = Promise.resolve({ id: 1 });

console.log(result, p);
EOF

npx tsc --noEmit return.ts
# (no errors)

# ============================================
# PART 2: PARAMETERS
# ============================================

cat > params.ts << 'EOF'
function createUser(name: string, age: number): void {}

type Params = Parameters<typeof createUser>;
// [name: string, age: number]

const args: Params = ['Alice', 30];

function log(level: string, ...messages: string[]): void {}

type LogParams = Parameters<typeof log>;
// [level: string, ...messages: string[]]

const logArgs: LogParams = ['INFO', 'a', 'b', 'c'];

console.log(args, logArgs);
EOF

npx tsc --noEmit params.ts
# (no errors)

# ============================================
# PART 3: AWAITED
# ============================================

cat > awaited.ts << 'EOF'
type A = Awaited<Promise<string>>;
type B = Awaited<Promise<Promise<number>>>;
type C = Awaited<string>;

const a: A = 'hello';
const b: B = 42;
const c: C = 'not a promise';

// Async function resolved type
async function getData(): Promise<{ data: string }> {
  return { data: 'x' };
}

type Result = Awaited<ReturnType<typeof getData>>;
// { data: string }

const r: Result = { data: 'x' };

console.log(a, b, c, r);
EOF

npx tsc --noEmit awaited.ts
# (no errors)

# ============================================
# PART 4: INSTANCE TYPE
# ============================================

cat > instance.ts << 'EOF'
class User {
  constructor(public name: string) {}

  greet(): string {
    return `Hi, ${this.name}`;
  }
}

type Instance = InstanceType<typeof User>;
// User

type CtorParams = ConstructorParameters<typeof User>;
// [name: string]

const user: Instance = new User('Alice');
const args: CtorParams = ['Bob'];

console.log(user.greet(), args);
EOF

npx tsc --noEmit instance.ts
# (no errors)

# ============================================
# PART 5: WRAPPERS
# ============================================

cat > wrappers.ts << 'EOF'
function memoize<T extends (...args: any[]) => any>(fn: T): T {
  const cache = new Map<string, ReturnType<T>>();
  return ((...args: Parameters<T>): ReturnType<T> => {
    const key = JSON.stringify(args);
    if (!cache.has(key)) {
      cache.set(key, fn(...args));
    }
    return cache.get(key);
  }) as T;
}

function slowAdd(a: number, b: number): number {
  console.log('computing');
  return a + b;
}

const cachedAdd = memoize(slowAdd);

console.log(cachedAdd(1, 2));
console.log(cachedAdd(1, 2));  // cached, no log

function withLogging<T extends (...args: any[]) => any>(fn: T): T {
  return ((...args: Parameters<T>): ReturnType<T> => {
    console.log('calling with', args);
    return fn(...args);
  }) as T;
}

const loggedAdd = withLogging(slowAdd);
loggedAdd(3, 4);
EOF

npx tsc --noEmit wrappers.ts
# (no errors)

# ============================================
# PART 6: ASYNC WRAPPERS
# ============================================

cat > async-wrapper.ts << 'EOF'
function withRetry<T extends (...args: any[]) => Promise<any>>(
  fn: T,
  attempts = 3
): (...args: Parameters<T>) => ReturnType<T> {
  return async (...args) => {
    let lastError: unknown;
    for (let i = 0; i < attempts; i++) {
      try {
        return await fn(...args);
      } catch (err) {
        lastError = err;
      }
    }
    throw lastError;
  };
}

async function fetchData(url: string): Promise<{ status: number }> {
  return { status: 200 };
}

const robustFetch = withRetry(fetchData);

async function main(): Promise<void> {
  const result = await robustFetch('/api');
  // result: { status: number }
  console.log(result.status);
}

main();
EOF

npx tsc --noEmit async-wrapper.ts
# (no errors)

# ============================================
# PART 7: FACTORY
# ============================================

cat > factory.ts << 'EOF'
class Service {
  constructor(private apiUrl: string, private timeout: number) {}

  fetch(): string {
    return `fetching from ${this.apiUrl} with timeout ${this.timeout}`;
  }
}

function createService<T extends new (...args: any[]) => any>(
  Ctor: T
): (...args: ConstructorParameters<T>) => InstanceType<T> {
  return (...args) => new Ctor(...args);
}

const makeService = createService(Service);
const s = makeService('/api', 5000);

console.log(s.fetch());
EOF

npx tsc --noEmit factory.ts
# (no errors)

# ============================================
# PART 8: COMPILE AND RUN
# ============================================

npx tsc return.ts params.ts awaited.ts instance.ts wrappers.ts async-wrapper.ts factory.ts
node return.js
# [ 42 Promise { { id: 1 } } ]

node params.js
# [ [ 'Alice', 30 ] [ 'INFO', 'a', 'b', 'c' ] ]

node awaited.js
# [ hello 42 not a promise { data: 'x' } ]

node instance.js
# [ Hi, Alice [ 'Bob' ] ]

node wrappers.js
# [ computing ]
# [ 3 ]
# [ 3 ]
# [ calling with [ 3, 4 ] ]
# [ computing ]
# [ 7 ]

node async-wrapper.js
# [ 200 ]

node factory.js
# [ fetching from /api with timeout 5000 ]

Quick Reference

The Four Utilities

UtilityExtracts
ReturnType<T>Function return type
Parameters<T>Function parameter tuple
Awaited<T>Resolved value of a promise
InstanceType<T>Instance from a constructor

Implementations

UtilityDefinition
ReturnType<T>T extends (...a: any) => infer R ? R : any
Parameters<T>T extends (...a: infer P) => any ? P : never
Awaited<T>Recursive thenable unwrap
InstanceType<T>T extends abstract new (...a) => infer R ? R : any

Constraints

UtilityConstraint
ReturnType<T>T extends (...args: any) => any
Parameters<T>T extends (...args: any) => any
Awaited<T>None
InstanceType<T>T extends abstract new (...args: any) => any

Related Utilities

UtilityExtracts
ConstructorParameters<T>Constructor parameter tuple
ThisParameterType<T>this parameter type
OmitThisParameter<T>Function without this

Common Patterns

PatternCode
Function returnReturnType<typeof fn>
Method returnReturnType<Class['method']>
Async resolvedAwaited<ReturnType<typeof fn>>
Function paramsParameters<typeof fn>
Method paramsParameters<Class['method']>
Class instanceInstanceType<typeof Class>
Constructor paramsConstructorParameters<typeof Class>
Wrapper<T extends Fn>(fn: T): T

Practical Examples

NeedCode
Type a variable from fn resulttype R = ReturnType<typeof fn>
Type wrapper args...args: Parameters<T>
Type cache valueMap<string, ReturnType<T>>
Type factory returnInstanceType<T>
Type factory argsConstructorParameters<T>
Get async valueAwaited<ReturnType<typeof asyncFn>>
Get single paramParameters<typeof fn>[0]
Get last paramParameters<typeof fn>[N]

Function Type Constraints

ConstraintMeaning
(...args: any) => anyAny function
(...args: any[]) => Promise<any>Any async function
new (...args: any) => anyAny constructor
abstract new (...args: any) => anyAny constructor (incl. abstract)

On Methods vs Functions

SourceType extraction
Functiontypeof fn
MethodClass['method']
Instance methodInstanceType<typeof C>['method']

Generic Functions

IssueSolution
ReturnType<typeof genericFn> โ†’ unknownUse typeof genericFn<T>
Unresolved type paramInstantiate with a concrete type

Error Cases

ErrorCause
does not satisfy the constraintNon-function to ReturnType
Expected function, got XNon-constructor to InstanceType
unknown resultUnresolved generic
any resultT is any

Wrapper Pattern

function wrap<T extends (...args: any[]) => any>(fn: T): T {
  return ((...args: Parameters<T>): ReturnType<T> => {
    // pre
    const result = fn(...args);
    // post
    return result;
  }) as T;
}

Async Wrapper Pattern

function asyncWrap<T extends (...args: any[]) => Promise<any>>(
  fn: T
): (...args: Parameters<T>) => ReturnType<T> {
  return async (...args) => {
    return fn(...args);
  };
}

Factory Pattern

function factory<T extends new (...args: any[]) => any>(
  Ctor: T
): (...args: ConstructorParameters<T>) => InstanceType<T> {
  return (...args) => new Ctor(...args);
}

When to Use Each

NeedUtility
Function’s returnReturnType
Function’s argsParameters
Async resolved valueAwaited
Class instance typeInstanceType
Constructor argsConstructorParameters

Best Practices

โœ… Do This:

// Use ReturnType to avoid duplicating
type User = ReturnType<typeof fetchUser>;                    // โœ…

// Use Parameters for wrappers
function wrap<T extends Fn>(fn: T): T {
  return ((...args: Parameters<T>) => fn(...args)) as T;
}                                                            // โœ…

// Use Awaited for async resolved types
type User = Awaited<ReturnType<typeof fetchUser>>;           // โœ…

// Use InstanceType in factories
function create<T extends new () => object>(C: T): InstanceType<T> {
  return new C();
}                                                            // โœ…

// Use ConstructorParameters for factory args
type Args = ConstructorParameters<typeof User>;              // โœ…

// Combine Awaited and ReturnType for async functions
type Value = Awaited<ReturnType<typeof asyncFn>>;            // โœ…

// Type wrappers preserving signatures
function memoize<T extends Fn>(fn: T): T {
  const cache = new Map<string, ReturnType<T>>();
  return ((...args: Parameters<T>) => {
    // ...
  }) as T;
}                                                            // โœ…

// Use typeof for function values
type R = ReturnType<typeof fn>;                              // โœ…

// Use indexed access for methods
type R = ReturnType<Service['method']>;                      // โœ…

// Verify with type checks
type _ = ReturnType<typeof fn> extends string ? true : false; // โœ…

โŒ Don’t Do This:

// Don't use ReturnType on non-functions
type Bad = ReturnType<string>;  // โŒ                            // โŒ

// Don't expect ReturnType to unwrap promises
type R = ReturnType<typeof asyncFn>;
// Promise<T>, not T โ€” use Awaited                              // โš ๏ธ

// Don't forget InstanceType for constructors
type Bad = ReturnType<typeof User>;  // โŒ wrong                  // โŒ

// Don't use Parameters on constructors
type Bad = Parameters<typeof User>;  // โš ๏ธ  empty or error        // โš ๏ธ

// Don't rely on generic ReturnType without instantiation
type R = ReturnType<typeof genericFn>;
// unknown โ€” T unresolved                                       // โš ๏ธ

// Don't duplicate what these utilities extract
type Params = [id: number, flag: boolean];  // โš ๏ธ
// Use Parameters<typeof fn> instead                            // โœ…

// Don't cast wrappers unsafely
function wrap(fn: any): any { }  // โš ๏ธ  loses types             // โš ๏ธ

// Don't over-nest utility calls
type Complex = Awaited<ReturnType<typeof fn<Awaited<...>>>>;    // โš ๏ธ

Common Pitfalls

PitfallProblemSolution
ReturnType on asyncGives Promise<T>Use Awaited
ReturnType on non-functionCompile errorCheck the input
Parameters on constructorWrong resultUse ConstructorParameters
InstanceType without typeofWrongInstanceType<typeof C>
Unresolved genericunknownInstantiate with concrete type
Mixing typeof and InstanceTypeWrong typeUnderstand the difference
Casting wrappersLoses type safetyUse T generics
Forgetting constraintsCompile errorAdd extends (...args: any) => any
Deep nestingUnreadableExtract named types
Expecting Awaited to change sync typesNo-opIt’s safe

Real-World Examples

1. Function return type

type R = ReturnType<typeof fetchUser>;

2. Method return type

type R = ReturnType<Service['method']>;

3. Async resolved

type Value = Awaited<ReturnType<typeof asyncFn>>;

4. Function parameters

type P = Parameters<typeof createUser>;

5. Method parameters

type P = Parameters<Service['method']>;

6. First parameter

type First = Parameters<typeof fn>[0];

7. Class instance

type I = InstanceType<typeof User>;

8. Constructor params

type P = ConstructorParameters<typeof User>;

9. Memoize wrapper

function memoize<T extends Fn>(fn: T): T {
  const cache = new Map<string, ReturnType<T>>();
  return ((...args: Parameters<T>) => {
    const k = JSON.stringify(args);
    if (!cache.has(k)) cache.set(k, fn(...args));
    return cache.get(k);
  }) as T;
}

10. Retry wrapper

function withRetry<T extends (...a: any[]) => Promise<any>>(fn: T): T {
  return (async (...args: Parameters<T>) => {
    for (let i = 0; i < 3; i++) {
      try { return await fn(...args); } catch { }
    }
  }) as T;
}

11. Timing wrapper

function timed<T extends Fn>(fn: T): T {
  return ((...args: Parameters<T>): ReturnType<T> => {
    const start = performance.now();
    const result = fn(...args);
    console.log(performance.now() - start);
    return result;
  }) as T;
}

12. Class factory

function factory<T extends new (...a: any[]) => any>(
  Ctor: T
): (...args: ConstructorParameters<T>) => InstanceType<T> {
  return (...args) => new Ctor(...args);
}

13. Typed event handler

class EventBus {
  on(event: string, handler: (...args: any[]) => void): void {}
}

type Handler = Parameters<EventBus['on']>[1];

14. Get async value type

async function fetchUser(): Promise<User> { /* ... */ }
type User = Awaited<ReturnType<typeof fetchUser>>;

15. Get promise type

type P = ReturnType<typeof fetchUser>;
// Promise<User>

16. Wrapper that preserves type

function debounce<T extends Fn>(fn: T, ms: number): T {
  let timer: number;
  return ((...args: Parameters<T>) => {
    clearTimeout(timer);
    timer = window.setTimeout(() => fn(...args), ms);
  }) as T;
}

17. Extract method parameters

type Params = Parameters<Map<string, number>['set']>;
// [key: string, value: number]

18. Method call signature

type Call = (...args: Parameters<Service['method']>) => ReturnType<Service['method']>;

19. Constructor type

type Ctor = new (...args: ConstructorParameters<typeof User>) => InstanceType<typeof User>;

20. Async wrapper with typed params

function promisify<T extends (...args: any[]) => any>(
  fn: T
): (...args: Parameters<T>) => Promise<ReturnType<T>> {
  return (...args) => Promise.resolve(fn(...args));
}

Visual: The Four Utilities

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  function fn(a: string, b: number): boolean  โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚
       โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ ReturnType<typeof fn>
       โ”‚           โ†’ boolean
       โ”‚
       โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ Parameters<typeof fn>
       โ”‚           โ†’ [a: string, b: number]
       โ”‚
       โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ fn itself

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Promise<Promise<string>>                    โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚
       โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ Awaited<...>
                   โ†’ string

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  class User { }                              โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚
       โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ InstanceType<typeof User>
                   โ†’ User

Visual: ReturnType

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  function greet(name: string): string {      โ”‚
โ”‚    return `Hello, ${name}`;                  โ”‚
โ”‚  }                                           โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚
       โ”‚  typeof greet
       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  (name: string) => string                    โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚
       โ”‚  ReturnType<...>
       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  string                                      โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Parameters

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  function createUser(                        โ”‚
โ”‚    name: string,                             โ”‚
โ”‚    age: number                               โ”‚
โ”‚  ): User { }                                 โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚
       โ”‚  Parameters<typeof createUser>
       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  [name: string, age: number]                 โ”‚
โ”‚                                              โ”‚
โ”‚  Indexed access:                             โ”‚
โ”‚  [0] โ†’ string                                โ”‚
โ”‚  [1] โ†’ number                                โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Awaited

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Promise<string>                             โ”‚
โ”‚       โ”‚                                      โ”‚
โ”‚       โ”‚  Awaited                             โ”‚
โ”‚       โ–ผ                                      โ”‚
โ”‚  string                                      โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Promise<Promise<number>>                    โ”‚
โ”‚       โ”‚                                      โ”‚
โ”‚       โ”‚  Awaited (recursive)                 โ”‚
โ”‚       โ–ผ                                      โ”‚
โ”‚  Promise<number>                             โ”‚
โ”‚       โ”‚                                      โ”‚
โ”‚       โ–ผ                                      โ”‚
โ”‚  number                                      โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  string (not a promise)                      โ”‚
โ”‚       โ”‚                                      โ”‚
โ”‚       โ”‚  Awaited                             โ”‚
โ”‚       โ–ผ                                      โ”‚
โ”‚  string (unchanged)                          โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: InstanceType

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  class User {                                โ”‚
โ”‚    constructor(public name: string) {}       โ”‚
โ”‚    greet() { }                               โ”‚
โ”‚  }                                           โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  typeof User                                 โ”‚
โ”‚  โ†’ constructor type                          โ”‚
โ”‚    new (name: string) => User                โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  InstanceType<typeof User>                   โ”‚
โ”‚  โ†’ User (instance type)                      โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  User                                        โ”‚
โ”‚  โ†’ the same instance type                    โ”‚
โ”‚                                              โ”‚
โ”‚  InstanceType<typeof User> === User          โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Wrapper Pattern

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  function memoize<T extends Fn>(fn: T): T {  โ”‚
โ”‚    const cache = new Map<string, ReturnType<T>>();โ”‚
โ”‚    return ((...args: Parameters<T>) => {     โ”‚
โ”‚      const key = JSON.stringify(args);       โ”‚
โ”‚      if (!cache.has(key)) {                  โ”‚
โ”‚        cache.set(key, fn(...args));          โ”‚
โ”‚      }                                       โ”‚
โ”‚      return cache.get(key);                  โ”‚
โ”‚    }) as T;                                  โ”‚
โ”‚  }                                           โ”‚
โ”‚                                              โ”‚
โ”‚  Parameters<T>   โ†’ wrapper's args            โ”‚
โ”‚  ReturnType<T>   โ†’ cache value type          โ”‚
โ”‚  T               โ†’ wrapper's type            โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Async Pipeline

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  async function fetchUser(): Promise<User>   โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚
       โ”‚  ReturnType
       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Promise<User>                               โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚
       โ”‚  Awaited
       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  User                                        โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Combined:                                   โ”‚
โ”‚                                              โ”‚
โ”‚  type User = Awaited<ReturnType<typeof fetchUser>>;โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Factory Pattern

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  class Service {                             โ”‚
โ”‚    constructor(url: string, t: number) {}    โ”‚
โ”‚  }                                           โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  function factory<T extends new (...a: any[]) => any>(โ”‚
โ”‚    Ctor: T                                   โ”‚
โ”‚  ): (...args: ConstructorParameters<T>) => InstanceType<T> {โ”‚
โ”‚    return (...args) => new Ctor(...args);    โ”‚
โ”‚  }                                           โ”‚
โ”‚                                              โ”‚
โ”‚  ConstructorParameters<T> โ†’ factory's args   โ”‚
โ”‚  InstanceType<T>          โ†’ factory's return โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  const makeService = factory(Service);       โ”‚
โ”‚  const s = makeService('/api', 5000);        โ”‚
โ”‚                                              โ”‚
โ”‚  s: Service                                  โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Method Extraction

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  class UserService {                         โ”‚
โ”‚    getUser(id: number): Promise<User> { }    โ”‚
โ”‚  }                                           โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  UserService['getUser']                      โ”‚
โ”‚  โ†’ (id: number) => Promise<User>             โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚
       โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ–บ Parameters<...>
       โ”‚        โ†’ [id: number]
       โ”‚
       โ””โ”€โ”€โ”€โ”€โ”€โ”€โ–บ ReturnType<...>
                โ†’ Promise<User>
                โ”‚
                โ”‚  Awaited
                โ–ผ
                User

Visual: Decision Flow

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Need a function's return type?              โ”‚
โ”‚       โ””โ”€โ”€ ReturnType<typeof fn>              โ”‚
โ”‚                                              โ”‚
โ”‚  Need a method's return type?                โ”‚
โ”‚       โ””โ”€โ”€ ReturnType<Class['method']>        โ”‚
โ”‚                                              โ”‚
โ”‚  Need the resolved async value?              โ”‚
โ”‚       โ””โ”€โ”€ Awaited<ReturnType<...>>           โ”‚
โ”‚                                              โ”‚
โ”‚  Need parameters?                            โ”‚
โ”‚       โ””โ”€โ”€ Parameters<typeof fn>              โ”‚
โ”‚                                              โ”‚
โ”‚  Need constructor parameters?                โ”‚
โ”‚       โ””โ”€โ”€ ConstructorParameters<typeof C>    โ”‚
โ”‚                                              โ”‚
โ”‚  Need an instance type?                      โ”‚
โ”‚       โ””โ”€โ”€ InstanceType<typeof C>             โ”‚
โ”‚                                              โ”‚
โ”‚  Writing a wrapper?                          โ”‚
โ”‚       โ””โ”€โ”€ Use Parameters<T> and ReturnType<T>โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

UtilityExtracts
ReturnType<T>Function’s return type
Parameters<T>Function’s parameter tuple
Awaited<T>Resolved value of a promise
InstanceType<T>Instance type of a constructor

Key takeaways:

  • ReturnType<T> gives what a function returns โ€” the async version gives Promise<T>
  • Parameters<T> gives a function’s parameter tuple โ€” index into it for one parameter
  • Awaited<T> unwraps promises, recursively โ€” non-promises pass through unchanged
  • InstanceType<T> gives the instance type from a constructor type โ€” the result of new
  • ConstructorParameters<T> is Parameters for constructors
  • All four are built on infer โ€” a conditional type capturing a part of the function or class type
  • Combine Awaited<ReturnType<T>> to get the resolved value of an async function
  • Wrappers use Parameters<T> and ReturnType<T> to preserve a function’s signature
  • Factories use ConstructorParameters<T> and InstanceType<T> to type input and output
  • Methods are accessed via Class['method'] for both Parameters and ReturnType
  • Generic functions return unknown from ReturnType unless instantiated with a concrete type
  • Constraints โ€” T extends (...args: any) => any โ€” keep the utilities safe

Remember: These four utilities answer the four questions you can ask about a function or class: what does it return, what does it take, what does the promise resolve to, and what does new produce? They’re all infer in different patterns. Use them to type wrappers, factories, and derived types without duplicating signatures. When the function changes, every consumer updates. That’s the value โ€” single source of truth, derived types that follow.


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!