Return Types

Annotate When the Contract Matters

Return Types

TS usually infers a function's return type. Annotate for public APIs — to lock in the contract.

3 min read Level 1/5 #typescript#return-types#functions
What you'll learn
  • Recognize the inferred return type
  • Annotate exports for clarity
  • Use `Promise<T>` for async functions

TS infers return types from your function’s body. Annotate when you want to lock in the contract.

Inferred

function add(a: number, b: number) {
  return a + b;
}
// inferred return: number

Hovering shows number. Inferred return is great for local helpers.

Annotated

function add(a: number, b: number): number {
  return a + b;
}

When the function is exported or part of a stable API, annotate the return. If a future change breaks the contract, the compiler tells you immediately:

export function add(a: number, b: number): number {
  return `${a + b}`;   // ✗ Type 'string' is not assignable to type 'number'.
}

Without the annotation, you’d silently change the API.

Async Functions Return Promises

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

The return type wraps in Promise<T> since async functions always return promises.

If you skip the annotation, TS infers Promise<User> from the body.

Functions That Return Nothing

function log(msg: string): void {
  console.log(msg);
}

void says “this function isn’t expected to return a useful value”. Whatever your function returns won’t be considered an error, but callers shouldn’t read it.

Functions That Never Return

function fail(msg: string): never {
  throw new Error(msg);
}

never for throw-only or infinite-loop functions.

Implicit Return

const double = (n: number): number => n * 2;

Arrow functions with no curly braces implicitly return the expression.

Up Next

Defaults and rest parameters.

Default & Rest Params →