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 promisegives 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
Userdirectly: When you have the class as a value (liketypeof User), you can’t always name the instance type directly.InstanceTypederives 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
ParametersandReturnTypepair 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 tupleReturnType<UserRepository['getUser']>โ the promiseAwaited<...>โ the resolved valueInstanceType<typeof UserRepository>โ the class instanceConstructorParameters<typeof UserRepository>โ the constructor argumentswithLoggingโ wraps any function, preserving its signature viaParameters<T>andReturnType<T>withRetryโ wraps an async function, preserving parameters and return typecreateRepositoryโ 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
| Utility | Extracts |
|---|---|
ReturnType<T> | Function return type |
Parameters<T> | Function parameter tuple |
Awaited<T> | Resolved value of a promise |
InstanceType<T> | Instance from a constructor |
Implementations
| Utility | Definition |
|---|---|
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
| Utility | Constraint |
|---|---|
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
| Utility | Extracts |
|---|---|
ConstructorParameters<T> | Constructor parameter tuple |
ThisParameterType<T> | this parameter type |
OmitThisParameter<T> | Function without this |
Common Patterns
| Pattern | Code |
|---|---|
| Function return | ReturnType<typeof fn> |
| Method return | ReturnType<Class['method']> |
| Async resolved | Awaited<ReturnType<typeof fn>> |
| Function params | Parameters<typeof fn> |
| Method params | Parameters<Class['method']> |
| Class instance | InstanceType<typeof Class> |
| Constructor params | ConstructorParameters<typeof Class> |
| Wrapper | <T extends Fn>(fn: T): T |
Practical Examples
| Need | Code |
|---|---|
| Type a variable from fn result | type R = ReturnType<typeof fn> |
| Type wrapper args | ...args: Parameters<T> |
| Type cache value | Map<string, ReturnType<T>> |
| Type factory return | InstanceType<T> |
| Type factory args | ConstructorParameters<T> |
| Get async value | Awaited<ReturnType<typeof asyncFn>> |
| Get single param | Parameters<typeof fn>[0] |
| Get last param | Parameters<typeof fn>[N] |
Function Type Constraints
| Constraint | Meaning |
|---|---|
(...args: any) => any | Any function |
(...args: any[]) => Promise<any> | Any async function |
new (...args: any) => any | Any constructor |
abstract new (...args: any) => any | Any constructor (incl. abstract) |
On Methods vs Functions
| Source | Type extraction |
|---|---|
| Function | typeof fn |
| Method | Class['method'] |
| Instance method | InstanceType<typeof C>['method'] |
Generic Functions
| Issue | Solution |
|---|---|
ReturnType<typeof genericFn> โ unknown | Use typeof genericFn<T> |
| Unresolved type param | Instantiate with a concrete type |
Error Cases
| Error | Cause |
|---|---|
does not satisfy the constraint | Non-function to ReturnType |
Expected function, got X | Non-constructor to InstanceType |
unknown result | Unresolved generic |
any result | T 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
| Need | Utility |
|---|---|
| Function’s return | ReturnType |
| Function’s args | Parameters |
| Async resolved value | Awaited |
| Class instance type | InstanceType |
| Constructor args | ConstructorParameters |
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
| Pitfall | Problem | Solution |
|---|---|---|
ReturnType on async | Gives Promise<T> | Use Awaited |
ReturnType on non-function | Compile error | Check the input |
Parameters on constructor | Wrong result | Use ConstructorParameters |
InstanceType without typeof | Wrong | InstanceType<typeof C> |
| Unresolved generic | unknown | Instantiate with concrete type |
Mixing typeof and InstanceType | Wrong type | Understand the difference |
| Casting wrappers | Loses type safety | Use T generics |
| Forgetting constraints | Compile error | Add extends (...args: any) => any |
| Deep nesting | Unreadable | Extract named types |
Expecting Awaited to change sync types | No-op | It’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
| Utility | Extracts |
|---|---|
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 givesPromise<T>Parameters<T>gives a function’s parameter tuple โ index into it for one parameterAwaited<T>unwraps promises, recursively โ non-promises pass through unchangedInstanceType<T>gives the instance type from a constructor type โ the result ofnewConstructorParameters<T>isParametersfor 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>andReturnType<T>to preserve a function’s signature - Factories use
ConstructorParameters<T>andInstanceType<T>to type input and output - Methods are accessed via
Class['method']for bothParametersandReturnType - Generic functions return
unknownfromReturnTypeunless 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!