Skip to content
Laviyx Learn

Try

TypeScript course menu

TypeScript Type Inference: When to Annotate and When to Let TypeScript Decide

Learn how TypeScript infers types from values, return statements and context: let vs const literal inference, inferred return types, contextual typing of callbacks, best common type for arrays, and guidelines on when explicit annotations help.

Beginner5 min readUpdated
On this page

TypeScript does not need an annotation on every line. It infers types from the code: if you write let count = 0, it knows count is a number. Good TypeScript code relies on inference most of the time and adds annotations where they add value.

Inferring from values#

TypeScript
let city = "Pune";          // string
let rating = 4.5;           // number
let isOpen = true;          // boolean

city = "Delhi";             // fine: still a string
console.log(city, rating, isOpen);
Output
Delhi 4.5 true

The inferred type is just as strict as a written one:

TypeScript
let rating = 4.5;
rating = "excellent";
Compiler error
error TS2322: Type 'string' is not assignable to type 'number'.

Hover over a variable in VS Code to see what type TypeScript inferred.

let versus const#

A const can never change, so TypeScript infers the exact value as its type (a literal type). A let can be reassigned, so it gets the general type:

TypeScript
let size = "medium";        // type: string
const level = "beginner";   // type: "beginner"

size = "large";
console.log(size, level);
Output
large beginner

Literal types become important for unions such as "small" | "medium" | "large"; see Literal Types.

Inferring return types#

TypeScript infers what a function returns:

TypeScript
function average(values: number[]) {    // return type inferred as number
  return values.reduce((a, b) => a + b, 0) / values.length;
}

const result = average([80, 90, 70]);   // result: number
console.log(result.toFixed(1));
Output
80.0

Parameters, however, cannot be inferred from how the function is used. Without an annotation, a parameter is an error in strict mode:

TypeScript
function double(n) {
  return n * 2;
}
Compiler error
error TS7006: Parameter 'n' implicitly has an 'any' type.

Contextual typing#

When a function is passed somewhere that already expects a certain function type, TypeScript infers its parameters from that context:

TypeScript
const names = ["asha", "ravi", "meera"];       // string[]

const capitalised = names.map((name) => name[0].toUpperCase() + name.slice(1));
//                              ^ name is inferred as string

console.log(capitalised);
Output
[ 'Asha', 'Ravi', 'Meera' ]

This is why callbacks to map, filter, addEventListener and so on rarely need annotations.

Arrays and objects#

TypeScript
const scores = [72, 95, 58];                     // number[]
const mixed = [1, "two", 3];                     // (string | number)[]
const user = { name: "Asha", age: 21 };          // { name: string; age: number }

user.age = 22;
console.log(scores.length, mixed.length, user);
Output
3 3 { name: 'Asha', age: 22 }

For an empty array, TypeScript cannot know what will go into it, so annotate it: const items: string[] = [];.

When to annotate#

SituationAnnotate?
Variable initialised with a valueUsually not; inference is enough
Function parametersYes, always
Exported functions and public APIsAnnotate the return type: it documents intent and catches mistakes inside the function
Empty arrays and objects to fill laterYes
Variables declared without a valueYes: let total: number;
Data from outside (JSON, APIs, user input)Yes, with a type you define and ideally validate

A useful habit: write the types you mean at boundaries (parameters, return values, data coming in), and let inference handle the rest.

Practice#

Practice exercise

For each variable, say what type TypeScript infers, then check with the compiler by assigning a wrong value to one of them:

TypeScript
let title = "TypeScript";
const version = 5;
const tags = ["types", "compiler"];
const course = { name: "TS", lessons: 39 };
Show solution
  • title: string (a let, so the general type).
  • version: 5 (a const, so the literal type).
  • tags: string[].
  • course: { name: string; lessons: number }.
TypeScript
const tags = ["types", "compiler"];
tags.push(42);
Compiler error
error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.

Key takeaway#

Finished this lesson?