🌱BeginnerTypeScript Basics
Type Inference
How TypeScript automatically deduces types from context and when to add explicit annotations.
Overview
TypeScript's type inference engine is remarkably powerful. It can figure out the type of a variable from its initializer, the return type of a function from its `return` statements, and the type of array elements from their contents all without a single type annotation.
This means you don't have to annotate everything. In fact, redundant annotations add noise without value. The guiding principle: **let TypeScript infer where it can; annotate where it adds clarity or prevents mistakes.**
Inference works well for: variable declarations with initializers, function return types, object literals, array literals. It works less well for: empty arrays (`[]`), function parameters (TypeScript can't infer these), `const` vs `let` (affects widening), and generic functions without hints.
Key Points
- •TypeScript infers the type of a `const` variable from its initializer no annotation needed
- •Function parameters are NOT inferred always annotate them
- •`const x = "hello"` → type `"hello"` (literal); `let x = "hello"` → type `string` (widened)
- •Return type inference: TypeScript infers return type from all `return` statements in the function
- •Contextual typing: TypeScript infers callback parameter types from how the callback is used
- •Empty arrays: `const arr = []` → `never[]`; annotate: `const arr: string[] = []`
- •`as const` narrow everything to literal types and make readonly: `["a","b"] as const`
Examples
Inference in action no annotations needed
// Variables
const name = "Alice"; // type: "Alice" (literal, const)
let city = "London"; // type: string (widened, let)
const pi = 3.14159; // type: 3.14159
let score = 0; // type: number
// Arrays
const nums = [1, 2, 3]; // type: number[]
const mixed = [1, "hello"]; // type: (number | string)[]
// Objects
const user = { id: 1, name: "Bob" };
// type: { id: number; name: string }
// Functions return type inferred
function double(x: number) { // parameter must be annotated
return x * 2; // return type: number (inferred)
}
const result = double(5); // type: number
console.log(result); // 10const vs let literal vs widened types
// const narrows to literal type
const status = "active"; // type: "active"
// status = "inactive"; // Error!
// let widens to primitive type
let mode = "active"; // type: string
mode = "inactive"; // ok
// Use this for exact value types
const directions = ["north", "south", "east", "west"] as const;
// type: readonly ["north", "south", "east", "west"]
// Each element is its literal type, not string
type Direction = typeof directions[number];
// type Direction = "north" | "south" | "east" | "west"Contextual typing callbacks
const numbers = [1, 2, 3, 4, 5];
// TypeScript infers 'num' as number from the array type
const doubled = numbers.map(num => num * 2);
// num: number (contextual typing from Array<number>.map)
numbers.forEach((item, index) => {
// item: number, index: number both inferred from context
console.log(`${index}: ${item}`);
});
// Event listeners (browser context)
document.addEventListener("click", (event) => {
// event: MouseEvent inferred from "click" event type
console.log(event.clientX, event.clientY);
});Common Mistakes
- Annotating everything explicitly `const name: string = "Alice"` is redundant; prefer `const name = "Alice"`
- Not annotating function parameters TypeScript cannot infer these and will use `any` with noImplicitAny disabled
- Relying on inference for empty arrays `const arr = []` gives type `never[]`, which won't accept pushes; annotate empty arrays
Note
A good rule: annotate function parameters and public API boundaries (exported functions, class methods). Let TypeScript infer everything else. This gives maximum type safety with minimum annotation clutter.
Exercises
Write TypeScript code to solve each exercise. Click Run to compile and execute it right here, then mark as done.
Loading editor…
Completed: 0 / 5 exercises
Knowledge Check
Question 1 of 4