Skip to content

Type Annotations and Type Inference

You do not have to annotate everything. TypeScript watches how you use values and infers their types automatically in many situations. Knowing when inference works and when explicit annotations are necessary is what separates noise from signal.

When you initialize a variable with a value, TypeScript infers its type from that value:

const score = 7; // inferred: number
const name = 'AceIt'; // inferred: string
const done = false; // inferred: boolean

These are identical to writing:

const score: number = 7;
const name: string = 'AceIt';
const done: boolean = false;

The explicit annotations add no information — TypeScript already knows the type from the right-hand side. Writing them anyway is noise.

Inference is reliable wherever TypeScript can see the initial value:

const questions = ['What is 2+2?', 'Name a planet']; // inferred: string[]
function double(n: number) {
return n * 2; // inferred return type: number
}

TypeScript infers the return type of double as number because multiplying a number by 2 always produces a number. You do not need to write : number after the closing parenthesis.

Function parameters. TypeScript cannot infer parameter types from the function body alone — the caller decides what is passed in. Parameters always need annotations:

function formatScore(score: number, total: number): string {
return `${score} / ${total} correct`;
}

Variables declared without initialization. If you declare a variable before assigning it, TypeScript infers any (the escape hatch type that disables checking). Annotate it instead:

let currentQuestion; // inferred: any — not useful
let currentQuestion: string; // explicit — TypeScript enforces it

When inference produces the wrong type. If TypeScript infers a wider type than you intended, annotate to be precise:

let difficulty = 'easy'; // inferred: string — could be reassigned to anything

You may want a more constrained type here. Module 02 covers how literal types and type aliases handle this.

Return types on complex functions. Annotating return types explicitly makes functions self-documenting and ensures you do not accidentally change what a function returns:

async function fetchQuestions(amount: number): Promise<string[]> {
// ...
}

Writing this out makes the contract visible to every caller without reading the implementation. In AceIt, fetchQuestions will eventually return Promise<TriviaQuestion[]> — a typed interface you will define in Module 03.

any disables TypeScript’s type checking for a value. It accepts any operation without error:

let value: any = 'hello';
value = 42; // no error
value.toFixed(2); // no error — but crashes at runtime if value is a string
value.nonExistent(); // no error — TypeScript trusts you

any is a valid escape hatch when you are migrating JavaScript to TypeScript or handling truly unpredictable data. But using it broadly defeats the purpose of TypeScript. Prefer specific types, and use unknown (covered in Module 06) for data whose type genuinely cannot be known at write time.

A simple heuristic that works for most TypeScript code:

  • Let TypeScript infer types for initialized variables and simple return values
  • Always annotate function parameters
  • Annotate return types on public-facing functions and async functions
  • Annotate variables declared without initialization

Following this rule keeps your code readable without drowning in redundant annotations.

Open quiz.ts from the previous lesson. For each variable and function:

  1. Remove any annotations that TypeScript can infer — check that the compiler still passes with no errors.
  2. Add explicit return type annotations to all three functions (buildUrl, formatScore, isHighScore).
  3. Declare a variable let lastAnswer; without a value. Check what type TypeScript infers. Add an explicit : string annotation and verify the compiler accepts it.
  • TypeScript infers types from initial values — you do not need to annotate what TypeScript can already see.
  • Function parameters cannot be inferred and always need annotations.
  • Variables declared without initialization get any if not annotated — avoid this.
  • Explicit return type annotations make function contracts visible and prevent accidental changes.
  • any disables type checking — use it sparingly and prefer unknown for genuinely uncertain data.