Annotate When the Contract Matters
Return Types
TS usually infers a function's return type. Annotate for public APIs — to lock in the contract.
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 →