Skip to content
Theory · Functions · lesson 5 of 10

Function types

In JavaScript a function is a value, like a number or a string. You put it in a variable, pass it to another function, keep it in an array. But what type does a variable holding a function have?

Picture a wall of vending machines at a station. Above one empty spot hangs a plate, "Takes two coins, gives a cup", and the machine itself is not there yet. Any machine that takes two coins and gives a cup can go there — no matter who built it or what is inside. A function type is such a plate without a machine: it describes which parameters a function takes and what it returns, and the function itself is put in later.

Writing a function type

Hover over add in the line const add = (a: number, b: number) => a + b;. The hover shows const add: (a: number, b: number) => number. The part after the colon is the function type. You can name it with type, like any other type:

TypeScript
type Op = (a: number, b: number) => number;

const add: Op = (a: number, b: number) => a + b;
const mul: Op = (x: number, y: number) => x * y;

function apply(op: Op, a: number, b: number): number {
    return op(a, b);
}

console.log(apply(add, 2, 3), apply(mul, 2, 3)); // 5 6

Let's take (a: number, b: number) => number apart:

  • the parentheses hold the parameters with their types, as in a function signature;
  • after the arrow => comes the result type: in a function type it follows the arrow, not a colon;
  • the names a and b are only for the reader: mul names its parameters x and y, and it still fits Op.

Now the example line by line:

  1. type Op gives the plate a name: "takes two numbers, returns a number".
  2. add and mul are two different functions, and both fit Op.
  3. The parameter op: Op in apply accepts any function of this type.
  4. apply(add, 2, 3) calls add and returns 5, and apply(mul, 2, 3) returns 6.

A function of the wrong kind

If a function's parameters or result don't match the plate, tsc won't let you put it in the variable:

TypeScript
type Op = (a: number, b: number) => number;

const glue: Op = (a: string, b: string) => a + b; // ✗
TEXT
error TS2322: Type '(a: string, b: string) => string' is not assignable to type 'Op'.

The message means: "a function of type (a: string, b: string) => string cannot be assigned to the type Op". apply will pass it two numbers, while the function expects strings. In the message tsc shows the function's type in the same arrow notation.

This is where the plate analogy ends. A machine's plate gives the coins no names. The parameters in a function type do have names, but tsc ignores them: it compares only the parameter types and the result type.

void in a function type

In the third theme a function with the annotation : void could not return a value: tsc answered with error TS2322. In a function type void behaves differently:

TypeScript
type Callback = (n: number) => void;

const saved: number[] = [];
const keep: Callback = (n: number) => saved.push(n);

const r = keep(5);
console.log(r, saved.length); // 1 1

The push method returns the array's new length, so the arrow returns a number. Still, it fits Callback, and at run time keep(5) returns 1. But r has the type void, so r + 1 gives error TS2365: the result of such a function must not be used. Passing r to console.log is allowed, since it accepts a value of any type, but tsc won't let you compute with it or call its methods.

void in a function type means "nobody uses the result", not "the function returns nothing". That makes callbacks convenient: the forEach method expects a function with a void result, and the arrow (n: number) => saved.push(n) fits it, even though push returns something. Interviewers ask about it like this: "Why does a function that returns a number fit the type () => void?"

Common mistake

The result type was written after a colon, as in a function declaration:

TypeScript
type Op = (a: number, b: number): number; // ✗
TEXT
error TS1005: '=>' expected.

The message means: "an arrow => was expected". A colon before the result type belongs in a function declaration, and a function type uses the arrow: (a: number, b: number) => number.

Key points

  • A function type is written as (a: number, b: number) => number: the parameters with their types, an arrow, the result type.
  • Any function with the same parameter and result types fits a function type; parameter names don't matter.
  • void in a function type means "the result is not used": a function that returns a value fits too.
  • In a function type the result type goes after =>, not after a colon.