DEV Community

Linas Spukas
Linas Spukas

Posted on

TypeScript Annotations and Inferences

Annotation and Inference are two TypeScript systems for referring value types to variables. Both of them tells you which value type assign to a variable. The critical difference is that the annotations you have to assign manually, while the Inference will do it automatically. In other words, annotations you have to write yourself, and Inference will be guessed and assigned by TypeScript.
In this article, we will analyse a few examples with annotations and Inference to better understand when and where to use them both.

Annotations

Type annotation is an explicit value type assignment to a variable. It has a syntax : Type, that must become after the variable name. You can write annotations for all value types manually: strings, booleans, arrays, functions. When you write an annotation, you are telling TypeScript that this particular variable should have this explicit type.

Primitive types

Here's an example how to write annotations for a primitive types:

let age: number = 30; // for numbers
let fruit: string = "kiwi"; // for strings
let flatEarth: boolean = false; // for booleans
let aliens: null = undefined; // for undefined or null values

Once we assign a type, the compiler will always check and make sure that the variable stays with it for the rest of its lifetime. If you try to reassign to another type, you will be notified that such reassignment is not valid:

Alt Text

Object types

Annotations for object types are a little bit different.

To set a type for Arrays you would write : Type[]. It will include only the specified type to the array, nothing more:

const price: number[] = [10, 20, 300]; // only array of numbers
const fruits: string[] = ['apples', 'banana', 'kiwi']; // only array of strings

Object literals has a syntax : { value: Type }:

const person = {
    name: 'Mike', 
    age: 30,
}

// Note that name and age types are separated by semicolon
const person: { name: string; age: number } = {
    name: 'Mike',
    age: 30,
}

Classes:

class Person { }

const person: Person = new Person();

Function annotations looks confusing the most. The syntax for specifying types for arguments and a return value: : (arg: Type) => Type.

// simple arrow function
const double = (num) => num * 2;

// type signature - : (num: number) => number
const double: (num: number) => number = (num) => num * 2;

// for a function without return value
const print = (value) => console.log(value);

// we specify special type: void
const print: (value: any) => void = (value) => console.log(value); 

Inference

TypeScript inference system helps to determine and assign a type for a variable without the need to do it manually. All the examples written above in the annotation section can be written without the actual annotations, and still, all the types would be assigned correspondently. The Inference happens when initialising variables and members, setting parameter default values. For example, in expression const name = "Mike", the name variable will automatically be inferred to be a type of String, and you can see that by hovering over it.

let name = "Mike";
let age = 30;

// both following reassignments will be rejected
// even if we have not explicitly assigned types for variables
name = 30; // trying to reassign a number for a string type variable
age = "Mike"; // trying to reassign a string to a number type variable 

Furthermore, Inference assigns the best common types for object-based types. For example, if you have an array of different types, all of them will be evaluated and assigned to the best possible matching types:

Alt Text

Usage

When to use annotation and Inference is straightforward. You should use Inference most of the time. There are only a few cases where Inference comes short, and it is impossible to determine a type.

The first case is when you are calling a function, and it returns any type.
For example, JSON.parse() method return any type, because it is hard to predict for TypeScript what value type will be parsed. Here you would have to annotate the return value explicitly:

const person = '{"name":"Mike","age":30}';

const result: { name: string; age: number }  = JSON.parse(person);

Another example is when we have to declare a variable first and assign or reassign value later:

let result: number | boolean;

if (Math.random() > 0.5) {
    result = true;
} else {
    result = 0;
}

Summing up

TypeScript is smart enough for evaluating and assigning the best possible matching types automatically. Most of the time, you do not need to worry about writing annotations manually, except the cases where unknown return values come from functions or variables must be declared before the initialisation.

Top comments (0)