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.
What type inference is
Section titled “What type inference is”When you initialize a variable with a value, TypeScript infers its type from that value:
const score = 7; // inferred: numberconst name = 'AceIt'; // inferred: stringconst done = false; // inferred: booleanThese 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.
When inference works well
Section titled “When inference works well”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.
When to annotate explicitly
Section titled “When to annotate explicitly”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 usefullet currentQuestion: string; // explicit — TypeScript enforces itWhen 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 anythingYou 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.
The any type
Section titled “The any type”any disables TypeScript’s type checking for a value. It accepts any operation without error:
let value: any = 'hello';value = 42; // no errorvalue.toFixed(2); // no error — but crashes at runtime if value is a stringvalue.nonExistent(); // no error — TypeScript trusts youany 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.
Practical rule
Section titled “Practical rule”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.
Exercise
Section titled “Exercise”Open quiz.ts from the previous lesson. For each variable and function:
- Remove any annotations that TypeScript can infer — check that the compiler still passes with no errors.
- Add explicit return type annotations to all three functions (
buildUrl,formatScore,isHighScore). - Declare a variable
let lastAnswer;without a value. Check what type TypeScript infers. Add an explicit: stringannotation 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
anyif not annotated — avoid this. - Explicit return type annotations make function contracts visible and prevent accidental changes.
anydisables type checking — use it sparingly and preferunknownfor genuinely uncertain data.