How to Get the Return Type of a Function in TypeScript

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.

Get the Return Type of a Function in TypeScript
  • 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.

How to Get the Return Type of a Function in TypeScript
  • 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.

Get the Return Type of a Function TypeScript
  • 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>; // string

Use 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 | number

You 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:

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.

ScenarioExample
Named functionReturnType<typeof myFunction>
Arrow function type aliastype R = ReturnType<MyFnType>
Async function Promise typetype R = ReturnType<typeof myAsyncFn>
Async function resolved typetype R = Awaited<ReturnType<typeof myAsyncFn>>
Manual overload signatureReturnType<(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 ReturnType to 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.

  1. 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.
  2. 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.

Doing these in your editor will cement how ReturnType behaves in slightly different situations.

You may also read:

51 Python Programs

51 PYTHON PROGRAMS PDF FREE

Download a FREE PDF (112 Pages) Containing 51 Useful Python Programs.

pyython developer roadmap

Aspiring to be a Python developer?

Download a FREE PDF on how to become a Python developer.

Let’s be friends

Be the first to know about sales and special discounts.