intermediate22 min

Typing Functions

Annotate parameters, returns, optional and default arguments, and functions passed as values.

What you'll learn

  • Type optional and default parameters correctly
  • Write a function type for a callback
  • Explain when an explicit return type is worth writing

Prerequisites

Explanation

Functions are where types pay off most, because a function is a contract between code written at different times by different people.

Parameters and returns

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

Parameters always need annotations. The return type is usually inferred, so : number here is optional — but writing it is often worth it. An explicit return type makes the compiler check the function body against your intention, so if you later add a branch that returns a string, the error appears inside the function you broke rather than at some distant call site.

Optional and default parameters

function greet(name: string, greeting?: string): string {
  return (greeting ?? "Hello") + ", " + name;
}

function greetWithDefault(name: string, greeting = "Hello"): string {
  return greeting + ", " + name;
}

A ? parameter is string | undefined and must be handled. A default parameter is different: the type is inferred from the default and the parameter is never undefined inside the body, because the default fills in. Prefer defaults when a sensible one exists — it removes a branch.

Optional parameters must come after required ones. There is no way to skip an earlier argument.

Functions as values

To pass a function around, you need a type for it. The syntax is an arrow between parameters and return type:

type Formatter = (value: string) => string;

function applyTwice(value: string, f: Formatter): string {
  return f(f(value));
}

Parameter names in a function type are documentation only — (value: string) => string and (input: string) => string are the same type. What matters is position, type, and count.

Callbacks are where this shows up constantly, and where inference helps: in items.map(x => x.length), TypeScript already knows x is a string because it knows items is string[]. You rarely annotate callback parameters inline.

void

void is the return type of a function that returns nothing useful:

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

It means "do not rely on the return value", not "returns undefined and I promise nothing else ever will".

Example

A default parameter, a named function type, and a callback whose parameter is inferred.

type Transform = (value: string) => string;

function shout(value: string): string {
  return value.toUpperCase() + "!";
}

function applyTwice(value: string, transform: Transform): string {
  return transform(transform(value));
}

function joinNames(names: string[], separator = ", "): string {
  return names.join(separator);
}

console.log(applyTwice("hey", shout));
console.log(joinNames(["Ada", "Grace", "Alan"]));
console.log(joinNames(["Ada", "Grace"], " & "));

// The callback parameter's type is inferred from the array's type:
const lengths = ["one", "three"].map((word) => word.length);
console.log(lengths.join("/"));

Try it yourself

Change applyTwice's callback to one returning a number and Run — the mismatch is caught at the call site.

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Guided exercise

Guided exercise

Write `buildTag(content: string, tag = "p"): string` returning `"<p>hello</p>"` style markup, using a default parameter so `buildTag("hello")` works.

Checks: buildTag is defined · buildTag("hello") uses the default p tag · An explicit tag overrides the default

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Stuck? Get a hint.

Independent exercise

Independent exercise

Define `type NumberTransform = (n: number) => number`. Write `applyAll(values: number[], f: NumberTransform): number[]` that returns a new array with `f` applied to each value.

Checks: applyAll is defined · Applies the transform to every value · plus 1 hidden check

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Stuck? Get a hint.

Common mistakes

  • Putting an optional parameter before a required one. Arguments are positional, so there is no way to skip one.
  • Annotating callback parameters that TypeScript already infers from the array being mapped.
  • Treating `void` as 'returns undefined'. It means the return value is not meant to be used.

Knowledge check

Knowledge check

1. In `function f(a: string, b = 2)`, what is the type of `b` inside the body?
2. Are `(value: string) => string` and `(input: string) => string` the same type?
3. Why write an explicit return type when it can be inferred?

Takeaway

Parameters always need types; explicit return types localise errors to the function you actually broke.

Summary

Annotate parameters, prefer default values over optional parameters where a sensible default exists, and describe callbacks with `(params) => return` function types. Return types are inferred but often worth stating.

References

Your notes

Notes save automatically.

Finished this lesson?

Mark it complete to track your progress and schedule a future review.

Next: Generics