Difficulty: Beginner
TypeScript has a powerful type inference engine that can automatically determine the type of a variable based on its initial value or the context in which it's used. When you write `const name = "Alice"`, TypeScript infers that `name` is of type `string` without you needing to write `: string`. This inference works for all primitive types, arrays, objects, function return types, and more.
The inference system distinguishes between `let` and `const` declarations. When you use `const`, TypeScript infers a literal type: `const x = 5` gives `x` the type `5` (not just `number`), and `const s = "hello"` gives `s` the type `"hello"` (not just `string`). This is because const variables cannot be reassigned, so TypeScript can be more specific. With `let`, the type is widened: `let x = 5` infers type `number` because `x` could later be assigned any number.
Contextual typing is another form of inference where TypeScript determines types from the surrounding context. For example, when you pass a callback to `array.map()`, TypeScript already knows the array's element type, so it infers the callback parameter type automatically. Similarly, event handlers in frameworks receive properly typed event objects without manual annotation. This contextual inference is one of the reasons TypeScript feels so natural to use.
The general guideline is: let TypeScript infer types when it can, and add explicit annotations when inference isn't sufficient or when you want to document your intent. Function parameters almost always need explicit annotations since TypeScript can't infer them from the function definition alone. Return types can usually be inferred, but adding them to public API functions improves documentation and catches accidental return type changes.
// TypeScript infers all of these types
const name = "Alice"; // type: "Alice" (literal)
let age = 30; // type: number (widened)
const isActive = true; // type: true (literal)
let scores = [90, 85, 92]; // type: number[]
// No annotations needed - TypeScript knows the types
const total = scores.reduce((sum, score) => sum + score, 0);
console.log(`${name}, age ${age}, total: ${total}`);
TypeScript infers types from initial values. const declarations get literal types while let declarations get widened types. Array methods like reduce also have their return types inferred.
const numbers = [1, 2, 3, 4, 5];
// TypeScript infers 'n' is number from the array type
const doubled = numbers.map(n => n * 2);
const evens = numbers.filter(n => n % 2 === 0);
const found = numbers.find(n => n > 3);
console.log("Doubled:", doubled.join(", "));
console.log("Evens:", evens.join(", "));
console.log("Found:", found);
In callbacks passed to array methods, TypeScript infers the parameter type from the array's element type. You don't need to write `(n: number) => ...` because TypeScript knows.
// Good: annotate function parameters (can't be inferred)
function calculateArea(width: number, height: number): number {
return width * height;
}
// Good: annotate when the inferred type is too wide
let status: "loading" | "success" | "error" = "loading";
// Good: annotate complex return types for clarity
function getUser(id: number): { name: string; age: number } | null {
if (id === 1) return { name: "Alice", age: 30 };
return null;
}
console.log(calculateArea(5, 3));
console.log(status);
console.log(getUser(1));
Function parameters need annotations since TypeScript can't infer them. Use explicit annotations when you want a more specific type than what would be inferred, or for complex return types.
Type inference, Implicit typing, Contextual typing, When to annotate