runtime.boot

Full Stack Systems
Production Mindset

CodeWithMihir

Engineering thoughtful products from interface to infrastructure.

CodeWithMihir

TypeScript Tutorial

TypeScript Type Inference Explained with Practical Examples

Learn TypeScript type inference, how TypeScript automatically detects variable, function, array, object, and return types, and when explicit annotations are still needed.

Welcome back! I am Mihir, and in this lesson we will learn Type Inference in TypeScript.

In the previous lesson, we learned how to manually write types using type annotations. But TypeScript is smart. You do not need to write types everywhere.

Type inference means TypeScript automatically figures out the type of a value based on how you write your code.


What is Type Inference?

Type inference is TypeScript's ability to automatically detect a type without an explicit annotation.

Example:

let course = "TypeScript";

We did not write:

let course: string = "TypeScript";

But TypeScript still understands that course is a string.

So this gives an error:

course = 100;

TypeScript inferred course as string, so assigning a number is not allowed.


Inference with Variables

let name = "Mihir";
let age = 25;
let isTeacher = true;

TypeScript infers:

VariableInferred Type
namestring
agenumber
isTeacherboolean

This means the following code is invalid:

age = "twenty five";
isTeacher = "yes";

TypeScript already knows the intended types.


const Inference vs let Inference

let and const are inferred slightly differently.

let

let role = "admin";

TypeScript infers:

string

Because let values can change.

role = "editor";

This is allowed.

const

const role = "admin";

TypeScript infers:

"admin"

This is a literal type, because a const primitive value cannot be reassigned.

So TypeScript knows the value is exactly "admin", not just any string.


Inference with Arrays

const scores = [90, 85, 100];

TypeScript infers:

number[]

This is allowed:

scores.push(95);

This is not allowed:

scores.push("excellent");

Because the array is inferred as an array of numbers.

Mixed Arrays

const values = [1, "two", true];

TypeScript infers:

(string | number | boolean)[]

That means each item can be a string, number, or boolean.

Usually, clean arrays with one type are easier to work with.


Inference with Objects

const user = {
  name: "Mihir",
  age: 25,
  isAdmin: true,
};

TypeScript infers this shape:

{
  name: string;
  age: number;
  isAdmin: boolean;
}

So this works:

console.log(user.name.toUpperCase());

But this does not:

console.log(user.email);

TypeScript warns because email does not exist on the inferred object type.


Inference with Function Return Types

TypeScript can infer return types.

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

TypeScript infers that add returns number.

So:

const result = add(10, 20);

result is inferred as number.

Another example:

function getGreeting(name: string) {
  return `Hello, ${name}`;
}

TypeScript infers the return type as string.


Function Parameters Are Different

TypeScript usually cannot infer function parameter types from just the function declaration.

function greet(name) {
  return `Hello, ${name}`;
}

If strict mode is enabled, TypeScript warns:

Parameter 'name' implicitly has an 'any' type.

So function parameters should usually be annotated:

function greet(name: string) {
  return `Hello, ${name}`;
}

TypeScript can infer the return type, but it often needs help with parameters.


Contextual Typing

Sometimes TypeScript infers types from context.

Example:

const numbers = [1, 2, 3];

numbers.map((number) => {
  return number * 2;
});

We did not write:

(number: number)

TypeScript knows number is a number because numbers is a number[].

This is called contextual typing.

Another example:

const names = ["Mihir", "Aarav", "Riya"];

names.forEach((name) => {
  console.log(name.toUpperCase());
});

TypeScript knows name is a string.


Inference with Destructuring

const product = {
  title: "Keyboard",
  price: 1499,
};

const { title, price } = product;

TypeScript infers:

  • title is string
  • price is number

This also works in function callbacks:

const products = [
  { title: "Keyboard", price: 1499 },
  { title: "Mouse", price: 799 },
];

products.forEach(({ title, price }) => {
  console.log(`${title}: ${price}`);
});

TypeScript understands the destructured values from the array item shape.


Annotation vs Inference

Both are useful.

Use inference for simple obvious values:

const name = "Mihir";
const age = 25;
const active = true;

Use annotations when you want to communicate intention:

let selectedUserId: number;

Use annotations for function parameters:

function calculateTotal(price: number, quantity: number) {
  return price * quantity;
}

Use custom types for object contracts:

type User = {
  id: number;
  name: string;
};

function printUser(user: User) {
  console.log(user.name);
}

Best Practice: Let TypeScript Infer When It Is Clear

This is too verbose:

const name: string = "Mihir";
const age: number = 25;
const isActive: boolean = true;

This is clean:

const name = "Mihir";
const age = 25;
const isActive = true;

The types are obvious, so inference is better.

But this should be annotated:

function createUser(name: string, email: string) {
  return {
    name,
    email,
  };
}

Function parameters are part of your function's public contract.


Common Mistake: Empty Arrays

Empty arrays need careful handling.

const users = [];

Depending on TypeScript settings and usage, this can become too loose or not useful.

Better:

type User = {
  id: number;
  name: string;
};

const users: User[] = [];

Now TypeScript knows this array should contain users.

users.push({ id: 1, name: "Mihir" });

Invalid:

users.push({ id: "one", name: "Mihir" });

Common Mistake: null Values

let selectedUser = null;

TypeScript may infer this too narrowly as null.

Better:

type User = {
  id: number;
  name: string;
};

let selectedUser: User | null = null;

Now selectedUser can be either:

  • a User
  • null

This is common in React state and API loading flows.


Quick Reference Summary

CodeInferred Type
let name = "Mihir"string
const name = "Mihir""Mihir"
let age = 25number
let active = trueboolean
const scores = [1, 2, 3]number[]
const values = [1, "two"](string | number)[]
function add(a: number, b: number) { return a + b }return type is number

Practice

Look at this code and guess the inferred types:

const course = "TypeScript";
let duration = 30;
const tags = ["typescript", "javascript", "web"];

function getCourseTitle(title: string) {
  return title.toUpperCase();
}

Answers:

  • course is "TypeScript"
  • duration is number
  • tags is string[]
  • getCourseTitle returns string

What You've Learned

You now understand:

  • What type inference means
  • How TypeScript infers variables
  • Difference between let and const inference
  • How arrays and objects are inferred
  • How function return types are inferred
  • Why function parameters usually need annotations
  • What contextual typing means
  • When to use inference vs annotations
  • Why empty arrays and null values often need explicit types

What's Next?

In the next lesson, we will learn Basic Types.

We will go deeper into string, number, boolean, null, undefined, any, unknown, never, and void.


Need Help?

  • Have questions, confusion, or want to know more? Contact me

Good TypeScript code uses a balance: annotate where it improves clarity, and let inference work where the type is obvious.