1. What is Type Inference?#
When you initialize a variable in TypeScript, the compiler analyzes the right-hand value and automatically assigns a type to the variable. You do not need to write : string or : number manually.
// TypeScript automatically infers 'name' as string
let name = "Rachmat";
// TypeScript automatically infers 'age' as number
let age = 30;
// TypeScript automatically infers 'isAdmin' as boolean
let isAdmin = true;If you attempt to reassign name to a number later, TypeScript throws a type error even though no explicit type annotation was written:
// Error: Type 'number' is not assignable to type 'string'.
// name = 42;
2. Literal Widening: const vs let#
The type inferred by TypeScript depends heavily on whether a variable is declared using let or const. This behavior is known as Literal Widening.
Inference with let (Widened Types)#
Because a let variable can be reassigned later, TypeScript infers a broad primitive type (string, number, boolean).
let city = "Jakarta";
// Inferred Type: string (because city could be changed to "Bandung" later)
Inference with const (Literal Types)#
Because a const variable can never be reassigned, TypeScript infers the narrowest possible type—the literal type representing the exact value.
const country = "Indonesia";
// Inferred Type: "Indonesia" (Literal Type, not string!)
let count = 10; // Inferred Type: number
const maxCount = 100; // Inferred Type: 100 (Literal Type)
| Declaration | Value | Inferred Type | Why? |
|---|---|---|---|
let role = "admin"; | "admin" | string | Can be reassigned to any string |
const role = "admin"; | "admin" | "admin" | Immutable value (Literal Type) |
3. Contextual Typing#
TypeScript is also smart enough to infer types in the reverse direction—from the context of where an expression appears. This is called Contextual Typing.
Consider an array .map() callback:
const numbers = [1, 2, 3, 4, 5];
// TypeScript automatically infers 'num' as 'number' based on the array context!
const doubled = numbers.map((num) => {
return num * 2;
});You do not need to write (num: number) in the callback; TypeScript knows that numbers is number[], so elements passed into .map() must be number.
Contextual Typing with Event Listeners#
window.addEventListener("click", (event) => {
// TypeScript automatically infers 'event' as MouseEvent!
console.log(event.clientX, event.clientY);
});4. When to Use Explicit Type Annotations#
If TypeScript’s inference engine is so powerful, when should you write explicit annotations? There are 3 critical scenarios where explicit annotations are required or strongly recommended:
Scenario 1: Uninitialized Variables (Preventing any)#
If you declare a variable without an immediate value, TypeScript cannot infer its type and defaults to any (which disables type safety).
// ❌ BAD: Inferred as 'any'
let data;
data = "Hello";
data = 42; // Allowed, but unsafe!
// 🟢 GOOD: Explicit Annotation
let secureData: string;
secureData = "Hello";
// secureData = 42; // Error: Type 'number' is not assignable to type 'string'.
Scenario 2: Function Parameters & Public Boundaries#
TypeScript cannot infer function parameters without context. You must explicitly annotate function parameters.
// ❌ Compiler Error: Parameter 'a' implicitly has an 'any' type.
// function add(a, b) { return a + b; }
// 🟢 GOOD: Annotated Parameters
function add(a: number, b: number): number {
return a + b;
}Scenario 3: Complex Objects & Contract Enforcement#
When declaring an object that represents a domain model or API payload, an explicit type annotation ensures that missing or misspelled properties throw errors at the declaration site, rather than downstream when the object is used.
interface UserProfile {
id: string;
email: string;
avatarUrl?: string; // Optional property
}
// 🟢 GOOD: Explicit annotation enforces object shape validation
const user: UserProfile = {
id: "usr_99",
email: "user@example.com",
// If you typo 'avatar_url', TS flags error RIGHT HERE on 'user'
};5. Should You Annotate Function Return Types?#
Whether to annotate function return types is a common debate. TypeScript can infer return types automatically:
// TS automatically infers return type as 'boolean'
function isAdult(age: number) {
return age >= 18;
}Best Practice Recommendation:#
- Annotate return types for Public APIs & Exported Functions: Annotating return types acts as a safety guard. If you accidentally return the wrong type inside a 100-line function, TS will flag an error inside the function body rather than breaking callers elsewhere.
- Omit return types for small, internal helper functions: Let TS infer simple single-line returns.
Summary & Next Steps#
In this episode:
- We learned how TypeScript infers primitive types automatically.
- We analyzed Literal Widening:
letinfers broad types (string), whileconstinfers literal types ("admin"). - We explored Contextual Typing in array methods and event handlers.
- We established the Golden Rule: Let TS infer variable assignments; annotate uninitialized variables, function parameters, and domain objects explicitly.
In Episode 4: Object Types and Interfaces, we will learn how to describe structured objects using TypeScript’s interface and type constructs.

