TypeScript - Type Inference



Type inference is a feature in TypeScript that allows the compiler to automatically determine (infer) the type of a variable, function or expression. TypeScript is an optionally static type programming language. You can declare variables, expressions, or functions without explicitly annotating their types. The compiler will automatically determine the types at the compile time.

The type inference can be done on the basis of a number of factors, including –

  • The type of values assigned to the variables.

  • The type of function parameters or arguments passed to the function.

  • The type of return value of the function.

  • The types of the object and properties.

Let's take a simple example as follows –

let x = 5;
let y = "Hello World!";

In the above code, the TypeScript compiler can infer that the type of variable x is number. This is because the variable x is being assigned a number. The compiler can also infer the type of y is string because the y is being assigned a string.

In the above example, TypeScript automatically infers the types of variables based on the values assigned to them.

Variable or Member Initialization

The type inference can be inferred using the variable and member initialization. The TypeScript compiler infers the type of the variable from the initialized value.

Example

Let's take an example of variable initialization as follows –

let x = 20;
let y = "Hello World!";
let z = true;
console.log("type of x: ", typeof x);
console.log("type of y: ", typeof y);
console.log("type of z: ", typeof z);

On compilation, it will generate the same JavaScript code.

The output of the above example code is as follows –

type of x:  number
type of y:  string
type of z:  boolean

Let’s have an example of object member initialization –

class Person {
  name = "Shahid";
  age = 35;
}
const p = new Person();
// Prints name and age
console.log(`${p.name}, ${p.age}`);

On compilation, it will generate following JavaScript code.

class Person {
    constructor() {
        this.name = "Shahid";
        this.age = 35;
    }
}
const p = new Person();
// Prints name and age
console.log(`${p.name}, ${p.age}`);

The output of the above code is as follows –

Shahid, 35

Function Default Parameter

The typescript compiler can also infer the type of the function parameters when the parameters are initialized with default values.

In the below example, the parameters x and y are initialized with default values. The compiler infers the type of x and y as number. This is because the initialized values are numbers.

function add(x = 10, y = 30){
   return x + y;
}
let res = add(2,3);
console.log(res);

On compilation, it will generate the same code in JavaScript.

The output of the above example code is as follows –

5

Now let's try to pass arguments as string values.

let res2 = add('2', '3');

The type of the parameters of the function add are inferred as number so when we pass arguments of type string to the function, it shows the following error –

Argument of type 'string' is not assignable to parameter of type 'number'.

Function Return Type

The TypeScript compiler infers the type of the return type of the function based on the type of the return value.

If the function doesn't return any value, then the return type is void.

In the example below, the function add accepts the two numbers and return their sum. As the sum of two numbers are number, so the type of return value is number. Hence the compiler infers the return type of the function add as number.

function add(x: number, y: number){
   return x + y;
}
let res1: number = add(2,3);
console.log(res1);

When we assign the return value of the function add to variable (res2) of type string, it will show an error.

let res2: string = add(2,3); 

The error is as follows –

Type 'number' is not assignable to type 'string'.

This is because the return type of the function add is number and we are assigning it to variable of string type.

Best Common Type: The Union Type

Type inference with variable initialization, function default parameters and return type is straightforward.

When TypeScript infers a type from multiple expressions, the type of the expressions is determined as the "best common type".

Let's understand this with the help of an example –

const a = [5, 10, "TypeScript"];

In the above example, the array contains values – 5, 10, and "TypeScript". To infer the type of the array, we must consider the type of each element. Here, we have choices for the type of the array as number, and string. The inferred type of the array should be the best common type for each value in the array.

The best common type has to be chosen from the provided candidate type. If there is no super type of all candidate types, the union type is used. Hence the inferred type of the above array is –

(number | string)[]

The above array can contain values of types number, and string only. If you try to add a value of type different from number and string, it will show an error.

Let's try to add a boolean value to the array.

const a = [5, 10, "TypeScript"];
a.push(true);

The above code will show the following compilation error –

Argument of type 'boolean' is not assignable to parameter of type 'string | number'.

Contextual Typing

Contextual typing is a feature in TypeScript that allows the compiler to infer the type of variable, parameter or expression based on the context where they are used. For example,

window.onmousedown = function (mouseEvent) {
  console.log(mouseEvent.button);
}

In the above example, the TypeScript infers the type the function expression on right hand side of the assignment using the type of the Window.onmousedown function. So it is able to infer the type of mouseEvent parameter as MouseEvent. This way TypeScript infers that mouseEvent has a button property and doesn't show a compilation error.

Contextual typing is very helpful in writing concise code without losing type safety.

Advertisements