RizTech Academy logo
RizTech Academy
FunctionsLesson 1 of 420 min

Typing parameters and return values

Functions are where types earn their keep most visibly: a function's signature is a contract — "give me these types, I return that type" — and TypeScript enforces it at every call. This lesson is typing a function's parameters and return value, and the one rule that decides most of it: annotate parameters, let the return type infer.

Parameters and return types

function greet(name: string, age: number): string {
  return `${name} is ${age}`;
}

Each parameter is annotated name: type, and the return type comes after the parameter list: : string. TypeScript now checks both ends — the call against the parameters, and the body against the return type:

greet("Kavita", 33);        // fine
greet("Kavita", "old");     // error: Argument of type 'string' is not assignable to parameter of type 'number'
greet("Kavita");            // error: Expected 2 arguments, but got 1

function bad(name: string): string {
  return name.length;       // error: Type 'number' is not assignable to type 'string'
}

Wrong argument types, wrong argument counts, and a body that returns the wrong type are all caught before the code runs. This is the core value: the signature is a promise, and the compiler holds both the caller and the implementation to it.

Parameters must be annotated; return types usually should not

The rule that runs through the whole course, applied to functions:

Parameters must be annotated. TypeScript cannot infer them — it has no way to know what a caller will pass. An un-annotated parameter becomes an implicit any (a silent hole), which strict mode correctly flags:

function greet(name) {       // error under strict: Parameter 'name' implicitly has an 'any' type
  return name.toUpperCase();
}

noImplicitAny (part of strict mode) makes this an error, forcing you to annotate. Always annotate parameters.

Return types are usually inferred, and that is fine — with a caveat. TypeScript infers the return type from the body:

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

You do not need to write : number — inference gets it right. So for small internal helpers, leaving the return type to inference is clean and idiomatic. But annotate the return type of a public or exported function, for two reasons: it documents the contract for callers, and it catches an accidental change to what the function returns. If you annotate : string and later edit the body to return a number, you get an error at the function — precise — rather than a confusing error at every call site. So: infer for private helpers, annotate for public API.

Typing this, briefly

In a standalone function, this is usually not a concern in TypeScript (arrow functions and modern code avoid it). When you do need it — an old-style callback that relies on this — TypeScript lets you type it with a special first "parameter" that is erased at runtime:

function handleClick(this: HTMLButtonElement, event: MouseEvent) {
  this.disabled = true;      // `this` is typed, not a real parameter
}

You will meet this rarely (mostly with DOM APIs and legacy code); modern TypeScript with arrow functions and classes sidesteps it. Know it exists so a this: parameter does not confuse you.

void — a function that returns nothing

A function that does not return a useful value has return type void:

function log(message: string): void {
  console.log(message);      // does something, returns nothing useful
}

void means "returns nothing you should use". You rarely write it — it is inferred for a function with no return (or a bare return;) — but it appears constantly in callback types (an event handler returns void). One subtlety worth knowing: a void return type in a callback is permissive — it allows a function that does return something to be used where void is expected, because the caller ignores the return value anyway:

const numbers = [1, 2, 3];
numbers.forEach(n => console.log(n));   // the arrow returns void (console.log returns undefined) — fine

This permissiveness is deliberate and occasionally surprising; for now, read void as "the return value is ignored".

Async functions return a Promise

A function marked async always returns a Promise, and TypeScript types it precisely:

async function fetchName(): Promise<string> {
  return "Kavita";           // even though you return a string, the function returns Promise<string>
}

An async function that "returns a string" actually returns a Promise<string> — the async wraps the value in a promise. TypeScript infers this, so you can write async function fetchName() and the return type is Promise<string> automatically. The async module goes deep on this; the point here is that async changes the return type to a Promise, and TypeScript tracks it. Annotating : Promise<string> on a public async function documents it clearly.

Check your work

What a function signature is. A contract — the parameter types and return type — that TypeScript enforces at every call and in the body.

What TypeScript checks. Argument types, argument count, and that the body returns the declared type.

Must parameters be annotated? Yes — TypeScript cannot infer them; an un-annotated one is an implicit any, which noImplicitAny (strict mode) flags.

Should return types be annotated? Infer for private helpers; annotate for public/exported functions (documents the contract, catches an accidental change at the function not the call sites).

What void means. Returns nothing useful — usually inferred, common in callback types; permissive about a callback that does return something.

What async does to the return type. Wraps it in a Promise — async () => string returns Promise<string>.

When you meet a this: parameter. Rarely — old-style callbacks relying on this; modern code with arrows and classes avoids it.

Practice

  1. Write greet(name: string, age: number): string. Call it with wrong types, too few arguments, and correctly; read each result.
  2. Return the wrong type from a function body and read the error at the function.
  3. Write a function with an un-annotated parameter under strict mode and read the implicit-any error. Annotate it.
  4. Write a small helper with no return annotation and confirm the return type is inferred. Then add a wrong explicit return type and see the error move to the function.
  5. Write a void function and confirm the type. Use an arrow returning a value in forEach and note it is allowed.
  6. Write an async function returning a string and confirm its inferred type is Promise<string>.
  7. For three functions, decide whether to annotate the return type and justify each choice.

Official documentation

Next: optional, default and rest parameters.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship