Skip to content
Theory · Functions · lesson 1 of 10

Parameters and the result

What you'll learn

What you'll learn:

  • spot a function that does not return the promised type on every path, and a call with an extra or a missing argument;
  • declare optional parameters, parameters with a default value and rest parameters;
  • describe a function type and write callbacks without needless annotations;
  • declare overloads and tell when a union is enough instead.
A vending machine with the plate number => string takes a coin and hands out a bar

Walk up to a tea machine. The plate by the coin slot says what it takes and what it gives: "Two 10-ruble coins — a cup of tea". You read the plate before you drop a coin: it tells you whether you have enough money and what you will get back.

A JavaScript function has no such plate. You can call area(width, height) with one argument: the second parameter gets undefined, 3 * undefined gives NaN, and the program carries on with that NaN. The mistake shows up later — wherever the NaN lands on the screen.

In TypeScript the function's author writes the plate. You already know how to give parameters and the result a type: the second theme started with that. In this lesson we'll see what tsc checks against such a plate — every path inside the function and every call from outside.

A function's signature

TypeScript
function area(width: number, height: number): number {
    return width * height;
}

console.log(area(3, 4)); // 12

The first line is the machine's plate. It is called the function signature: the name, the parameters with their types and the result type. Let's take it apart:

  • area is the function's name;
  • (width: number, height: number) are two parameters, both numbers: the machine takes exactly two coins;
  • : number after the parentheses is the result type: the function promises to return a number.

tsc uses the signature to check the function from two sides:

  1. Inside the function, that every path to the end of the body returns the promised type.
  2. Outside, that every call passes as many arguments as the function has parameters, of the right types.

The result on every path

A function can finish in different places: at one return, at another, or at the last line of the body if no return was reached. In the last case JavaScript returns undefined. tsc walks every path and checks that each one returns the type from the signature:

TypeScript
function grade(score: number): string { // ✗
    if (score >= 50) {
        return "зачёт";
    }
}
TEXT
error TS2366: Function lacks ending return statement and return type does not include 'undefined'.

The message means: "the function lacks a return at the end, and its result type does not include undefined". When score is below 50 the condition fails, the function reaches the end of its body and returns undefined. Yet the signature promised a string. That is why the error sits on the result type in the first line: that is the promise being broken.

The fix is to return a string on the second path too, for example by adding return "незачёт"; after the if.

Without the : string annotation the function itself would raise no error. TypeScript would infer the result type "зачёт" | undefined, and the undefined would surface wherever the result is used as a string. A result annotation moves the check into the function — where the mistake was made.

The number of arguments

In JavaScript a function can be called with any number of arguments: missing ones get undefined, extra ones are lost. TypeScript checks every call against the signature:

TypeScript
function area(width: number, height: number): number {
    return width * height;
}

console.log(area(3)); // ✗
TEXT
error TS2554: Expected 2 arguments, but got 1.

The message means: "2 arguments were expected, but 1 was passed". If JavaScript ran this call, height would get undefined, and area would return NaN. tsc won't let an extra argument through either: area(3, 4, 5) gives the same error with the text Expected 2 arguments, but got 3.

This is where the machine analogy ends. A real machine checks the coins at the moment you pay, while tsc checks the calls against the signature before the program runs. At run time JavaScript still accepts any number of arguments: the check lived only in tsc.

Common mistake

The if branches cover every case you have in mind, and tsc still reports an error:

TypeScript
function sign(n: number): string { // ✗
    if (n > 0) {
        return "плюс";
    } else if (n < 0) {
        return "минус";
    } else if (n === 0) {
        return "ноль";
    }
}
TEXT
error TS2366: Function lacks ending return statement and return type does not include 'undefined'.

It seems a number can only be positive, negative or zero. But tsc does not check whether the conditions cover every number: after the last else if it still sees a path without a return. And that path is real: all three comparisons are false for NaN, so sign(NaN) would return undefined. Replace the last check with else, or write a return after all the branches.

Key points

  • A function signature is the name, the parameters with their types and the result type; tsc checks both the function body and every call against it.
  • If even one path reaches the end of the body without a return, a function with the result type string gets error TS2366.
  • A call with a missing or an extra argument is error TS2554, although JavaScript would accept it silently.
  • A chain of else if with a return in each branch ends with an else branch or a return after all the branches.