Skip to content
Theory · Annotations and type inference · lesson 1 of 9

Type annotations

What you'll learn

What you'll learn:

  • write the type of a variable, a parameter and a function's result;
  • tell where TypeScript infers a type on its own and where you have to write it;
  • explain why let a = 5 gets the type number while const b = 5 gets the literal type 5;
  • tell a const that forbids a new value apart from an object that cannot change.
An open jar labelled number waits for more tiles, while a sealed jar labelled 5 holds a single tile

Picture a kitchen shelf of jars. Each jar has a label: "Salt", "Sugar", "Buckwheat". The label says what may go into the jar before anyone opens it. If someone starts pouring sugar into the jar labeled "Salt", the mistake is noticed at once, not when the soup turns out sweet.

JavaScript variables have no such labels. Today price holds a number, tomorrow someone writes the string "free" into it, and the program breaks somewhere else entirely—where price.toFixed(2) is called. To find the cause, you have to walk back through the code and look for who put a string there, and when.

In TypeScript you can write the label yourself. A type annotation is : type written after a name; it tells the compiler which type of values is allowed there. The colon reads as "has the type": count: number means "count has the type number".

Three places for an annotation

An annotation goes in three places: on a variable, on a function parameter and on a function's result. This example has all three:

TypeScript
let count: number = 0;

function withDelivery(price: number): number {
    return price + 300;
}

count = count + 1;
console.log(count, withDelivery(1200)); // 1 1500

The program compiles without errors and prints 1 1500. Let's take each place in turn.

A variable

The line let count: number = 0; has three parts:

  • let count—an ordinary declaration, as in JavaScript;
  • : number—the annotation: only numbers may be written into count;
  • = 0—the initial value, which must be a number too.

From then on, the compiler checks every assignment against the label. count = count + 1 puts a number into a number variable, and the check passes. The line count = "two" would give error TS2322, which you already met in the first theme.

A function parameter

price: number inside the parentheses is a label on the function's input. To the function body it promises that price holds a number, and it requires whoever calls the function to pass exactly a number:

TypeScript
function withDelivery(price: number): number {
    return price + 300;
}

withDelivery("1200"); // ✗
TEXT
error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.

The message means: "an argument of type string cannot be passed to a parameter of type number". tsc reports the error on the line of the call, although the function is declared above it. If JavaScript ran this call, "1200" + 300 would join the strings, and you would get "1200300" instead of 1500.

A function's result

The annotation after the parameter list—): number—is a label on the output. The function promises to return a number, and the compiler checks every return in its body:

TypeScript
function withDelivery(price: number): number {
    return "Итого: " + (price + 300); // ✗
}
TEXT
error TS2322: Type 'string' is not assignable to type 'number'.

The error sits on the line with return: a function that promised a number cannot return a string. You don't have to search the calls of withDelivery for the cause—the error shows where the promise is broken.

Who reads the labels

This is where the jar analogy ends. A label on a jar can be overlooked, while tsc checks an annotation every time it compiles the file. But the JavaScript it writes no longer has annotations: when the program runs, nobody checks the types.

Common mistake

The annotation is taken for a conversion, and the string is expected to become a number:

TypeScript
let quantity: number = "5"; // ✗
TEXT
error TS2322: Type 'string' is not assignable to type 'number'.

An annotation converts nothing; it only checks. A string, say from a form field, is first turned into a number—Number("5")—and then the result goes into a variable of type number.

Key points

  • A type annotation is : type written after a name; the colon reads as "has the type".
  • An annotation goes on a variable (let count: number), a parameter (price: number) and a function's result (): number).
  • tsc checks every assignment, every call and every return against the annotation.
  • An annotation converts nothing: the string "5" cannot be written into a variable of type number.