Module 21: Type Inference Deep Dive
Understanding how TypeScript automatically infers types helps you write cleaner, more maintainable code with minimal annotations.
1. Basic Type Inference
// Variable inference
let name = "Alice"; // string
let age = 25; // number
let isActive = true; // boolean
// Array inference
let numbers = [1, 2, 3]; // number[]
let mixed = [1, "two", true]; // (string | number | boolean)[]
2. Best Common Type
let items = [1, 2, 3, null]; // (number | null)[]
let values = [new Date(), "2024-01-01"]; // (string | Date)[]
// With explicit supertype
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
let pets = [new Dog(), new Cat()]; // (Dog | Cat)[]
let animals: Animal[] = [new Dog(), new Cat()]; // Animal[]
3. Contextual Typing
window.addEventListener("click", (event) => {
// event is inferred as MouseEvent
console.log(event.clientX, event.clientY);
});
const numbers = [1, 2, 3];
numbers.forEach((num) => {
// num is inferred as number
console.log(num.toFixed(2));
});
4. Return Type Inference
function add(a: number, b: number) {
return a + b; // Return type inferred as number
}
function getUser() {
return { name: "Alice", age: 25 };
// Return type inferred as { name: string; age: number; }
}
async function fetchData() {
return "data"; // Return type inferred as Promise<string>
}
5. Generic Inference
function identity<T>(value: T): T {
return value;
}
let num = identity(42); // T inferred as number
let str = identity("hello"); // T inferred as string
// Multiple generics
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
let result = pair("age", 25); // [string, number]
6. Inference from Usage
// Function parameter inference
const processUser = (user: { name: string; age: number }) => {
return user.name.toUpperCase();
};
// TypeScript infers the return type is string
7. Inference with Conditional Types
type Unpromise<T> = T extends Promise<infer U> ? U : T;
declare function unwrap<T>(value: T): Unpromise<T>;
let str = unwrap(Promise.resolve("hello")); // string
let num = unwrap(42); // number
8. Literal Type Inference
let direction = "up"; // string (widened)
const direction2 = "up"; // "up" (literal)
// Force literal type
let direction3 = "up" as const; // "up"
// Object as const
const config = {
host: "localhost",
port: 3000
} as const;
// { readonly host: "localhost"; readonly port: 3000; }
9. Inference in Generics
function map<T, U>(arr: T[], fn: (item: T) => U): U[] {
return arr.map(fn);
}
let numbers = [1, 2, 3];
let strings = map(numbers, n => n.toString());
// U inferred as string from return type of n.toString()
10. Type Widening
let x = null; // any (widened)
let y = undefined; // any (widened)
const z = null; // null (not widened)
// Prevent widening
let value = "hello" as const; // "hello"
Key Takeaways
✅ TypeScript infers types from values and context
✅ Best common type for unions
✅ Contextual typing in callbacks
✅ Generic inference from arguments
✅ as const prevents type widening
Practice Exercises
Exercise 1: Infer Complex Types
function createPerson(name: string, age: number) {
return {
name,
age,
greet() {
return `Hello, I'm ${this.name}`;
}
};
}
type Person = ReturnType<typeof createPerson>;
Next Steps
In Module 22, we'll explore the TypeScript Compiler API for programmatically working with TypeScript code.