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

Type inference

In the previous lesson you labeled every jar yourself. But the contents are often visible without a label. If buckwheat was poured into a jar, a helper can label it: they see what is inside and write "Buckwheat" for you.

In TypeScript the compiler is that helper. let count: number = 0 says "number" twice: once in the annotation and once more with the zero on the right. The zero is enough for the compiler. When the compiler works out a type from the code on its own—from a variable's initial value or from what a function returns—this is called type inference.

A type from the initial value

TypeScript
let count = 0;
let title = "Отчёт";
count = count + 1;
title = title + " за май";
console.log(count, title); // 1 Отчёт за май

There are no annotations here, but the variables do have types. Let's go through the example line by line:

  1. let count = 0;—the right side is a number, and TypeScript infers the type number for count.
  2. let title = "Отчёт";—the right side is a string, and title gets the type string.
  3. count = count + 1; puts a number into a number—the check passes.
  4. title = title + " за май"; puts a string into a string—it passes too.
  5. console.log prints 1 Отчёт за май.

An inferred type is checked as strictly as a written one. The line count = "two"; would give the same error TS2322 here as with the annotation let count: number = 0.

How to see an inferred type

The helper writes a label, but you don't see it in the code. To read it, hover over the name in an editor—VS Code or the TypeScript Playground. Over count the hover shows let count: number, over title—let title: string.

The hover is a handy way to check yourself. Before guessing a variable's type, hover over it: the hover shows what the compiler knows.

A function's result type

The helper reads not only initial values but also return:

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

const total = withDelivery(1200);
console.log(total); // 1500

The function has no ): number annotation, but TypeScript sees the expression after return: price + 300 is a number plus a number, so the result is a number too. That is why total also gets the type number.

The type of the parameter price is written out here. A parameter without a default value has no initial value, and the helper has nothing to read.

A variable without a value

Sometimes a variable is declared without an initial value and filled in later:

TypeScript
let total;
total = 1200;
total = "бесплатно";
console.log(total); // бесплатно

There is nothing to read here, and the hover over total on the first line shows let total: any. any is a special "anything goes" type: such a variable takes a number and a string alike, and tsc says nothing.

Such a jar has no label: the helper only remembers what was poured in last. That is why the hover over total on the console.log line shows let total: string, while total still takes anything you write into it.

If the variable needs a type, write it yourself: with the declaration let total: number; the line total = "бесплатно"; gives error TS2322.

Interviewers often ask what type inference is. The short answer: the compiler works out a type from a variable's initial value and from a function's return. Where there is nothing to read, you write the type yourself.

Common mistake

Expecting the type to change together with the value, as in JavaScript:

TypeScript
let price = 1200;
price = "бесплатно"; // ✗
TEXT
error TS2322: Type 'string' is not assignable to type 'number'.

TypeScript inferred the type of price once—from the initial value 1200—and the type does not change after that. Text for such a price goes into a separate variable, for example let priceLabel = "free";.

Key points

  • Type inference: TypeScript works out a variable's type from its initial value—let count = 0 gets number.
  • An inferred type is checked as strictly as an annotation and does not change after the declaration.
  • A function's result type is inferred from the expression after return.
  • A variable without a value has the type any, so you write the type of such a variable yourself: let total: number;.