When I’m building real TypeScript apps for clients in the US, I rarely write types just once. A function that fetches data for a React component might also feed a Redux selector, a service layer, and a couple of unit tests. If I hard‑code the same return type in three or four places, I know I’m going to forget to update one of them later.
That’s where TypeScript’s ReturnType utility really helps. It lets me ask TypeScript what a function returns and reuse that type everywhere, without repeating myself.
What is ReturnType in TypeScript?
TypeScript has a set of built‑in utility types that help you transform other types. ReturnType is one of them.
In plain English:
ReturnType takes a function type and gives you back “whatever this function returns”.
You’ll usually use it like this:
type Result = ReturnType<typeof myFunction>;
Behind the scenes, it’s implemented using conditional types and infer, but you don’t need to remember the internal definition to use it effectively. What you should remember is:
- You must pass a function type to ReturnType
- The easiest way to get that type from a real function is
typeof
Basic usage: ReturnType<typeof myFunction>
Let’s start with a simple example.
function getUserName(userId: number): string {
// Imagine this calls a database or API
return "John Doe";
}
// Extract the return type of getUserName
type UserNameType = ReturnType<typeof getUserName>;
// UserNameType is: string
const name: UserNameType = getUserName(101);
console.log(name);I executed the above example code and added the screenshot below.

- typeof getUserName turns the function value into its function type
- ReturnType<typeof getUserName> gives you the return type of that function
- You can now reuse UserNameType anywhere you need that type
I like this pattern because it keeps one source of truth:
- If later I change getUserName to return an object { id, name }, every place using UserNameType automatically updates.
Use ReturnType with explicitly typed functions
A lot of teams prefer to explicitly annotate return types for their public functions. That’s a good practice, and it works nicely with ReturnType.
function calculateTotalPrice(price: number, taxRate: number): number {
const totalPrice = price * (1 + taxRate);
return totalPrice;
}
// TypeScript sees this as: (price: number, taxRate: number) => number
type CalculateTotalPriceFn = typeof calculateTotalPrice;
// Extract the return type
type TotalPrice = ReturnType<typeof calculateTotalPrice>; // number
const orderTotal: TotalPrice = calculateTotalPrice(100, 0.07);I executed the above example code and added the screenshot below.

- The return type : number documents the function clearly for the team
- ReturnType<typeof calculateTotalPrice> lets you reuse the return type without repeating it
- During refactors, you change the function once, and your types follow
Use ReturnType with inferred return types
You don’t have to write the return type by hand for every function. TypeScript can often infer it perfectly well.
function getFullName(firstName: string, lastName: string) {
// Inferred return type: string
return `${firstName} ${lastName}`;
}
// Extract the inferred return type
type FullName = ReturnType<typeof getFullName>; // string
const fullName: FullName = getFullName("Alice", "Johnson");I executed the above example code and added the screenshot below.

- The function body is simple and clearly returns a single, obvious type
- You want clean code, but still need to reuse the return type elsewhere
In my own projects, I often let helpers infer their return types and then use ReturnType where I need the type elsewhere.
Use ReturnType with async functions and Awaited
Async functions are everywhere in modern TypeScript apps. ReturnType still works, but you’ll usually want the resolved value, not the Promise.
Here’s a realistic example from an API layer:
// API client function
async function fetchUser(userId: number) {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error("Failed to fetch user");
}
return response.json() as Promise<{
id: number;
name: string;
email: string;
}>;
}
// This is the Promise type returned by fetchUser
type FetchUserPromise = ReturnType<typeof fetchUser>;
// Promise<{ id: number; name: string; email: string; }>
// Use Awaited to get the resolved value type
type User = Awaited<FetchUserPromise>;
// { id: number; name: string; email: string; }
function showUserProfile(user: User) {
console.log(`User: ${user.name} (${user.email})`);
}
- ReturnType<typeof myAsyncFn> gives you Promise<Something>
- Wrap that in Awaited<> to get
Something
I use this pattern a lot when I want my React components, services, or tests to work with the resolved data type.
Use ReturnType with function type aliases and arrow functions
Sometimes you define function types as aliases, especially if you reuse them in multiple places.
type TransformUserName = (name: string) => {
upper: string;
length: number;
};
const transformUserName: TransformUserName = (name) => ({
upper: name.toUpperCase(),
length: name.length,
});
// Extract the return type of the TransformUserName function type
type TransformUserNameResult = ReturnType<TransformUserName>;
// { upper: string; length: number }
const result: TransformUserNameResult = transformUserName("Michael");This is useful when:
- You share a function type across multiple implementations
- You want to define the return type only once and reuse it consistently
Arrow functions in variables work similarly:
const buildUrl = (path: string, params: Record<string, string>) => {
const searchParams = new URLSearchParams(params);
return `${path}?${searchParams.toString()}`;
};
type BuildUrlReturn = ReturnType<typeof buildUrl>; // stringUse ReturnType with overloaded functions
Overloaded functions are a bit more advanced. TypeScript separates:
- Overload signatures (what callers see)
- Implementation signature (the actual function body)
Let’s look at a simple overload:
// Overload signatures
function processData(data: string): string;
function processData(data: number): number;
// Implementation
function processData(data: string | number) {
if (typeof data === "string") {
return data.toUpperCase();
}
return data * 2;
}
// typeof processData refers to the implementation signature
type ProcessDataReturn = ReturnType<typeof processData>;
// string | number
Notice: ReturnType<typeof processData> gives you string | number, because the implementation returns either type.
If you want the return type of a specific overload, you can define that overload’s function type and pass it to ReturnType.
type ProcessDataNumber = (data: number) => number;
type ProcessDataNumberReturn = ReturnType<ProcessDataNumber>; // number
type ProcessDataString = (data: string) => string;
type ProcessDataStringReturn = ReturnType<ProcessDataString>; // string
This is handy when you want a very precise return type for a particular call pattern, without changing the underlying function.
A slightly more advanced pattern
If you’re comfortable with conditional types, you can create a helper that behaves a bit like ReturnType but works with overloaded types:
type Overloaded = {
(value: string): string;
(value: number): number;
};
type ReturnOf<T> = T extends (...args: any[]) => infer R ? R : never;
// For an overloaded type, this becomes a union of all return types
type OverloadedReturn = ReturnOf<Overloaded>;
// string | numberYou may not need this every day, but it’s nice to know what’s possible when you start designing more advanced type utilities.
Real‑world use cases in apps
Now, let me show you a few places where I regularly use ReturnType in real projects.
1. Reuse API response types
I like my API clients to be the single source of truth for data shapes. Then I let everything else depend on those types.
// API client: fetch a list of orders
async function fetchOrders() {
const response = await fetch("/api/orders");
if (!response.ok) {
throw new Error("Failed to fetch orders");
}
return response.json() as Promise<
{
id: number;
total: number;
status: "pending" | "shipped" | "delivered";
}[]
>;
}
type FetchOrdersPromise = ReturnType<typeof fetchOrders>;
type OrderList = Awaited<FetchOrdersPromise>;
function getTotalRevenue(orders: OrderList) {
return orders.reduce((sum, order) => sum + order.total, 0);
}
Here:
- fetchOrders defines the order shape once
- OrderList is derived from it
- If you change the structure in
fetchOrders, your dependent code, updates automatically through types
2. Type selectors and mappers
In UI apps (React, Vue, etc.), I often write selectors or mapping functions and then reuse their return types.
type User = {
id: number;
name: string;
state: "CA" | "NY" | "TX" | "WA";
isAdmin: boolean;
};
function selectAdminNames(users: User[]) {
return users.filter((u) => u.isAdmin).map((u) => u.name);
}
// Reuse the result type of the selector
type AdminNames = ReturnType<typeof selectAdminNames>; // string[]
function logAdmins(names: AdminNames) {
console.log("Admins:", names.join(", "));
}This pattern works well with Redux selectors, Zustand selectors, or any central store logic.
3. Share service logic between environments
Sometimes I write shared logic that runs both on the server and in a front‑end project. I want the types to match exactly.
function buildUserSummary(user: User) {
return {
id: user.id,
label: `${user.name} (${user.state})`,
isAdmin: user.isAdmin,
};
}
type UserSummary = ReturnType<typeof buildUserSummary>;
function printUserSummary(summary: UserSummary) {
console.log(summary.label, summary.isAdmin ? "[Admin]" : "");
}Both server and client can import UserSummary and be confident they’re using the same shape.
Common mistakes and gotchas
I see the same few issues pop up again and again when people start using ReturnType.
1. Forgot to pass a function type
This is the most common mistake:
// ❌ Invalid: missing generic argument
type T1 = ReturnType;
// Error: Type 'ReturnType' is not generic
// ❌ Also invalid: passing a value instead of a type
function getConfig() {
return { apiUrl: "/api", timeout: 5000 };
}
type T2 = ReturnType<getConfig>;
// Error: 'getConfig' refers to a value, but is being used as a type here
// ✅ Correct: use typeof
type Config = ReturnType<typeof getConfig>;
// { apiUrl: string; timeout: number }
Quick rule of thumb:
- If you’re passing a real function (not a type alias), you almost always want typeof in front of it.
2. Confuse “specifying” a return type with “getting” a return type
Sometimes people say “I specified the return type using ReturnType” when they just manually wrote : number.
These are two different things:
// Here I manually specify the return type
function sum(a: number, b: number): number {
return a + b;
}
// Here I *get* the return type from the function
type SumReturn = ReturnType<typeof sum>; // number
- The : number on the function is just a normal annotation
- ReturnType<typeof sum> is TypeScript reading that annotation and giving you the type back
3. Expect overload‑aware behavior by default
As we saw earlier, ReturnType<typeof overloadedFn> uses the implementation signature, not each overload separately.
If you want per‑overload types, define function types for specific overloads and apply ReturnType to those.
You can use this table as a quick reference.
| Scenario | Example |
|---|---|
| Named function | ReturnType<typeof myFunction> |
| Arrow function type alias | type R = ReturnType<MyFnType> |
| Async function Promise type | type R = ReturnType<typeof myAsyncFn> |
| Async function resolved type | type R = Awaited<ReturnType<typeof myAsyncFn>> |
| Manual overload signature | ReturnType<(x: number) => number> |
Best practices for function return types in TypeScript
Here are some guidelines I follow on real projects:
- Annotate return types on public APIs
- Libraries, exported helpers, and shared services should have explicit : ReturnType annotations like : number, : User, etc.
- Use
ReturnTypeto avoid duplication- When multiple parts of your app depend on the same function result, derive the type using ReturnType instead of retyping it manually.
- Let TypeScript infer simple internal helpers
- For small internal functions, letting TypeScript infer the return type keeps your code clean.
- Combine ReturnType with Awaited for async functions
- This makes it easy to work with resolved data types instead of raw Promises.
- Be intentional with overloads
- Decide whether you want the union return type or a specific overload, and model your types accordingly.
If you want to test your understanding, here are a couple of small exercises you can try right now.
- Config exercise
- Write a function getConfig that returns an object with env: “development” | “production” and apiUrl: string.
- Use ReturnType to define a Config type.
- Write another function printConfig, that takes a Config and logs a friendly message.
- Overload exercise
- Create an overloaded function formatValue that:
- Returns a string when given a number (for example: “$50.00”).
- Returns a string when given a Date (for example: “2026-05-26”).
- Use ReturnType with manual function types to get each overload’s return type.
- Create an overloaded function formatValue that:
Doing these in your editor will cement how ReturnType behaves in slightly different situations.
You may also read:
- How to Check the Type of a Variable in TypeScript?
- How to Set Default Values for TypeScript Types?
- Define and Use TypeScript Interface Array of Objects
- How to Use TypeScript Interface Function Properties?

Bijay Kumar is an experienced Python and AI professional who enjoys helping developers learn modern technologies through practical tutorials and examples. His expertise includes Python development, Machine Learning, Artificial Intelligence, automation, and data analysis using libraries like Pandas, NumPy, TensorFlow, Matplotlib, SciPy, and Scikit-Learn. At PythonGuides.com, he shares in-depth guides designed for both beginners and experienced developers. More about us.