ZetCode

TypeScript Type Inference

last modified October 15, 2026

TypeScript's type inference automatically determines the types of variables, parameters, and return values. This feature reduces the need for explicit type annotations while maintaining type safety. This tutorial explores type inference with practical examples.

Type inference is TypeScript's ability to deduce types based on the context. For example, if you assign a number to a variable, TypeScript infers its type as number. This reduces boilerplate code while ensuring type safety.

Variable Initialization

TypeScript infers the type of a variable based on its initial value.

variable_inference.ts
let count = 10;  // TypeScript infers `count` as `number`
console.log(typeof count);

In this example, TypeScript infers count as a number because it's initialized with the numeric literal 10. This inference happens at compile time, allowing TypeScript to enforce type safety without an explicit annotation.

The typeof operator confirms the runtime type as "number," matching the inferred type. If you later tried to assign a string to count (e.g., count = "ten"), TypeScript would flag an error, showcasing how inference reduces verbosity while maintaining strict typing. This is ideal for simple variables where the initial value clearly indicates the intended type.

$ npx tsx variable_inference.ts
number

Function Return Types

TypeScript infers the return type of a function based on its implementation.

return_type_inference.ts
function add(a: number, b: number) {
    return a + b;  // TypeScript infers return type as `number`
}

console.log(add(5, 10));

The add function's return type is inferred as number because it performs addition on two number parameters, and the + operator yields a numeric result. TypeScript analyzes the function body at compile time, deducing the type without requiring an explicit : number annotation after the parameter list.

The output, 15, aligns with this inference. If the function returned a different type (e.g., "result"), TypeScript would error unless explicitly typed otherwise. This demonstrates how inference simplifies function definitions while ensuring type consistency based on implementation.

$ npx tsx return_type_inference.ts
15

Object Properties

TypeScript infers the types of object properties based on their values.

object_inference.ts
const user = {
    name: "Alice",
    age: 30
};  // TypeScript infers `user` as `{ name: string, age: number }`

console.log(user.name);

TypeScript infers the user object's type as { name: string, age: number } by examining the initial values: "Alice" (a string) and 30 (a number). This structural inference happens automatically, allowing access to properties like user.name with full type checking—attempting user.name = 42 would fail.

The output, "Alice," reflects the inferred string type. This capability reduces the need for explicit interfaces or type annotations for straightforward objects, making code concise yet safe, especially for data structures with clear initialization patterns.

$ npx tsx object_inference.ts
Alice

Array Types

TypeScript infers the type of array elements based on their initial values.

array_inference.ts
const numbers = [1, 2, 3];  // TypeScript infers `numbers` as `number[]`
console.log(numbers[0]);

The numbers array is inferred as number[] because all its initial elements (1, 2, 3) are numbers. TypeScript examines the array literal and assigns a uniform type to the elements, enabling type-safe operations like indexing (numbers[0]). Attempting to push a string (e.g., numbers.push("four")) would trigger a compile-time error.

The output, 1, confirms the first element's type. This inference simplifies array declarations, eliminating the need for : number[] while preserving type safety, which is particularly useful for homogeneous collections.

$ npx tsx array_inference.ts
1

Union Types

TypeScript infers union types when a variable can hold multiple types.

union_inference.ts
let value = Math.random() > 0.5 ? "Hello" : 42;  // Inferred as `string | number`
console.log(value);

TypeScript infers value as string | number because the ternary expression can resolve to either "Hello" (a string) or 42 (a number), depending on the random condition. This union type reflects all possible outcomes, allowing value to be used in contexts expecting either type, but restricting operations to those common to both (e.g., toString works, but toUpperCase requires type narrowing).

The output varies per execution, illustrating runtime flexibility within the inferred type bounds. This shows how inference handles dynamic assignments while maintaining type safety.

$ npx tsx union_inference.ts
Hello

Function Parameters

TypeScript does not infer parameter types from how they are used inside the function. Parameters need an annotation; the return type is then inferred.

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

console.log(greet("Alice"));

Without the annotation, name would implicitly have the any type; using it in a template literal does not make it a string. With noImplicitAny (part of strict), TypeScript reports an error for such a parameter. With the string annotation, calling greet(42) fails at compile time, and TypeScript infers the return type as string from the template literal. Parameter types are inferred only from context, for example in callbacks (see the next section) or from default values.

$ npx tsx parameter_inference.ts
Hello, Alice!

Contextual Typing

TypeScript infers types based on the context in which a function is used.

contextual_typing.ts
const names = ["Alice", "Bob", "Charlie"];
names.forEach(name => {
    console.log(name.toUpperCase());  // TypeScript infers `name` as `string`
});

TypeScript uses contextual typing to infer name as string in the forEach callback, based on the type of names (string[]). The forEach method expects a callback where the first parameter matches the array's element type, so TypeScript deduces name accordingly.

This allows toUpperCase to be called without errors, while calling a method that strings do not have, such as name.toFixed(2), would fail at compile time. The output logs uppercase names (e.g., "ALICE", "BOB", "CHARLIE"), showing how context drives inference, streamlining code in common iteration patterns.

$ npx tsx contextual_typing.ts
ALICE
BOB
CHARLIE

Generic Functions

TypeScript infers generic types based on the arguments passed to a function.

generic_inference.ts
function identity<T>(arg: T): T {
    return arg;
}

const result = identity("Hello");  // TypeScript infers `T` as `string`
console.log(result);

The identity function uses a generic type T, which TypeScript infers as string when called with "Hello". This inference binds T to the argument's type, ensuring the return type matches (string here). You could call identity(42) and T would be number, showcasing flexibility.

The output, "Hello," reflects the inferred type. TypeScript's generic inference eliminates the need for explicit type arguments (e.g., identity<string>), making the code concise while preserving type safety across different invocations.

$ npx tsx generic_inference.ts
Hello

Literal Types

TypeScript infers literal types for variables assigned specific, immutable values, typically with const. This feature narrows the type to the exact value, offering precision beyond broad types like string or number, which is especially useful for constants or constrained options.

literal_inference.ts
const direction = "left";  // TypeScript infers `direction` as `"left"`
const statusCode = 200;    // TypeScript infers `statusCode` as `200`
const isActive = true;     // TypeScript infers `isActive` as `true`

console.log(direction);
console.log(statusCode);
console.log(isActive);

// direction = "right";    // Error: Type '"right"' is not assignable to type '"left"'
// statusCode = 404;       // Error: Type '404' is not assignable to type '200'

TypeScript infers direction as the literal type "left", statusCode as 200, and isActive as true because these const-declared variables are assigned specific, immutable values.

Unlike let, which would infer broader types (string, number, boolean), const locks the type to the exact literal, preventing reassignment—e.g., direction = "right" or statusCode = 404 would fail at compile time, as shown in the commented errors.

The output ("left", 200, true) reflects these precise values. Literal types shine in scenarios like defining fixed options (e.g., HTTP status codes, directions in a game) or mimicking enums without extra syntax, enhancing type safety and autocompletion in IDEs. However, this precision is exclusive to const; mutable variables lose this granularity, defaulting to wider types.

$ npx tsx literal_inference.ts
left
200
true

Complex Object Inference

TypeScript infers complex object types based on their structure.

complex_object_inference.ts
const person = {
    name: "Alice",
    age: 30,
    address: {
        city: "New York",
        zip: "10001"
    }
};  // TypeScript infers a complex object type

console.log(person.address.city);

TypeScript infers person as { name: string, age: number, address: { city: string, zip: string } } by recursively analyzing the object's structure. The nested address object gets its own inferred type based on "New York" and "10001". This allows safe access to person.address.city, with TypeScript catching errors like person.address.city = 42. The output, "New York," confirms the inference. This deep inference simplifies working with complex data structures, reducing the need for explicit interfaces while maintaining robust type checking.

$ npx tsx complex_object_inference.ts
New York

Type Inference with Conditionals

TypeScript infers types in conditional branches, adapting to control flow.

conditional_inference.ts
function getValue(flag: boolean) {
    if (flag) {
        return "yes";  // Inferred as `string` in this branch
    }
    return 42;  // Inferred as `number` in this branch
}  // Overall return type inferred as `string | number`

console.log(getValue(true));

In getValue, TypeScript infers the return type as string | number by combining the types from each conditional branch: "yes" (string) if flag is true, and 42 (number) if false. Within each branch, the type is narrower, but the function's overall type reflects all possibilities. The output, "yes" for true, matches one inferred case (running with false would yield 42). This control-flow-based inference ensures flexibility while alerting developers to handle both outcomes, showcasing TypeScript's ability to adapt types dynamically.

$ npx tsx conditional_inference.ts
yes

Inference with Destructuring

TypeScript infers types when destructuring objects or arrays.

destructuring_inference.ts
const point = { x: 10, y: 20 };
const { x, y } = point;  // TypeScript infers `x` and `y` as `number`
console.log(x + y);

When destructuring point, TypeScript infers x and y as number based on the object's properties (10 and 20). The inferred type of point is { x: number, y: number }, and destructuring carries those types forward. This allows x + y to compute 30 without errors, while x = "ten" would fail. The output, 30, validates the inference. This feature streamlines destructuring by automatically typing variables, making it intuitive for working with structured data without extra annotations.

$ npx tsx destructuring_inference.ts
30

Inference with Default Parameters

TypeScript infers types from default parameter values in functions.

default_param_inference.ts
function describe(name = "Guest") {
    return `Welcome, ${name}`;  // TypeScript infers `name` as `string`
}

console.log(describe());
console.log(describe("Alice"));

The name parameter in describe is inferred as string because its default value, "Guest", is a string. TypeScript uses this default to set the type, allowing name to be used in a string context (template literal) and accepting string arguments like "Alice".

Calling describe(42) would error due to type mismatch. The output shows "Welcome, Guest" (default) and "Welcome, Alice" (explicit), demonstrating how default values drive inference, simplifying function signatures while ensuring type consistency.

$ npx tsx default_param_inference.ts
Welcome, Guest
Welcome, Alice

Best Practices

Source

TypeScript Type Inference Documentation

This tutorial covered TypeScript type inference with practical examples. Use these techniques to write cleaner, safer code.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all TypeScript tutorials.