TypeScript Type Inference

Automatic type detection by TypeScript

🧠 What is Type Inference?

Type Inference is TypeScript's intelligent ability to automatically determine and assign types to variables, function returns, and expressions without requiring explicit type annotations, making code cleaner and more maintainable.


// TypeScript infers the type automatically
let message = 'Hello';  // inferred as string
let count = 42;         // inferred as number
let isActive = true;    // inferred as boolean
                                    

Output:

✅ Types automatically inferred without annotations

Key Type Inference Concepts

📝

Variable Inference

Automatic type from initial value

let name = 'John';
// name is string
🔄

Return Type Inference

Function return types inferred

function add(a: number, b: number) {
  return a + b;  // returns number
}
🎯

Contextual Typing

Type inferred from context

window.onmousedown = (event) => {
  // event is MouseEvent
};
🏆

Best Common Type

Infer from multiple values

let arr = [1, 2, 3];
// arr is number[]

🔹 Basic Type Inference

TypeScript automatically infers types from values:

// Primitive types
let name = 'Alice';        // string
let age = 25;              // number
let isStudent = true;      // boolean
let nothing = null;        // null
let notDefined = undefined; // undefined

// Arrays
let numbers = [1, 2, 3];           // number[]
let names = ['Alice', 'Bob'];      // string[]
let mixed = [1, 'two', true];      // (string | number | boolean)[]

// Objects
let person = {
  name: 'John',
  age: 30
};
// person is { name: string; age: number; }

// No need for explicit types!
let greeting = 'Hello';
// greeting.toUpperCase();  // TypeScript knows it's a string

Output:

✅ All types automatically inferred

🔹 Function Return Type Inference

TypeScript infers return types from function bodies:

// Return type inferred as number
function add(a: number, b: number) {
  return a + b;
}

// Return type inferred as string
function greet(name: string) {
  return `Hello, ${name}!`;
}

// Return type inferred as boolean
function isEven(num: number) {
  return num % 2 === 0;
}

// Return type inferred as string | number
function getValue(condition: boolean) {
  if (condition) {
    return 'yes';
  }
  return 42;
}

// Return type inferred as void
function logMessage(msg: string) {
  console.log(msg);
}

// Arrow functions also infer return types
const multiply = (x: number, y: number) => x * y;  // returns number
const getName = () => 'John';  // returns string

🔹 Contextual Typing

Type inferred from the context where value is used:

// Event handler - event type inferred
window.onmousedown = function(event) {
  // event is automatically MouseEvent
  console.log(event.button);
};

// Array method callbacks
const numbers = [1, 2, 3, 4, 5];

// num is inferred as number
numbers.forEach(num => {
  console.log(num.toFixed(2));
});

// item is inferred as number, returns boolean
const evens = numbers.filter(item => item % 2 === 0);

// value is inferred as number, returns number
const doubled = numbers.map(value => value * 2);

// Promise callbacks
fetch('/api/data')
  .then(response => {
    // response is inferred as Response
    return response.json();
  })
  .then(data => {
    // data is inferred as any (from json())
    console.log(data);
  });

🔹 Best Common Type

TypeScript finds the best common type from multiple values:

// Array with multiple types
let mixed = [1, 2, 3, 'four'];
// Type: (string | number)[]

// Array with objects
let items = [
  { name: 'Apple', price: 1.5 },
  { name: 'Banana', price: 0.8 }
];
// Type: { name: string; price: number; }[]

// Conditional expressions
let value = Math.random() < 0.5 ? 'hello' : 42;
// Type: string | number

// Class instances
class Dog {
  bark() {}
}

class Cat {
  meow() {}
}

let pets = [new Dog(), new Cat()];
// Type: (Dog | Cat)[]

// Null/undefined in arrays
let nullableNumbers = [1, 2, null, 4];
// Type: (number | null)[]

🔹 Type Inference with Generics

Generic types can be inferred from arguments:

// Generic function
function identity<T>(arg: T): T {
  return arg;
}

// Type argument inferred as string
let output1 = identity('hello');

// Type argument inferred as number
let output2 = identity(42);

// Array generic inference
function getFirst<T>(arr: T[]): T {
  return arr[0];
}

let firstNumber = getFirst([1, 2, 3]);      // number
let firstName = getFirst(['a', 'b', 'c']);  // string

// Multiple type parameters
function pair<T, U>(first: T, second: U): [T, U] {
  return [first, second];
}

let result = pair('hello', 42);  // [string, number]

// Promise inference
async function fetchUser() {
  return { id: 1, name: 'John' };
}

// Return type inferred as Promise<{ id: number; name: string; }>
let user = fetchUser();

🔹 When to Use Explicit Types

Sometimes explicit types are better than inference:

// ❌ Inference might be too broad
let value = null;  // Type: null (not useful)

// ✅ Better with explicit type
let value: string | null = null;

// ❌ Unclear intent
let data = {};  // Type: {}

// ✅ Clear intent with explicit type
let data: { name: string; age: number } = {
  name: 'John',
  age: 30
};

// ❌ Function parameter needs type
function greet(name) {  // Error: Parameter needs type
  return `Hello, ${name}`;
}

// ✅ Explicit parameter type
function greet(name: string) {
  return `Hello, ${name}`;
}

// ✅ Explicit return type for documentation
function calculateTotal(items: number[]): number {
  return items.reduce((sum, item) => sum + item, 0);
}

🧠 Test Your Knowledge

What type does TypeScript infer for: let x = [1, 2, "three"]?