Annotations

When to Add `: Type` (and When Not To)

Annotations

Annotations explicitly declare a value's type. They're required in some places, optional in others. Knowing where to add them is half of writing good TS.

4 min read Level 1/5 #typescript#annotations#types
What you'll learn
  • Add annotations to variables, parameters, returns
  • Know when annotations are required
  • Let inference do the rest

A type annotation is : Type after a name. It tells TS what type something is. Annotations are required in a few places and optional (but sometimes helpful) elsewhere.

Where Annotations Live

let count: number = 0;             // variable

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

const greet = (name: string): string => `Hi, ${name}`;

Required: Function Parameters

Parameters CANNOT be inferred from inside the function alone — TS can’t know what callers will pass. So you annotate them:

function double(n: number) {     // n must be annotated
  return n * 2;
}

The return type CAN be inferred from n * 2 — annotation optional. Many teams annotate returns anyway, to make API intent explicit.

Optional: Variables With Initializers

When you initialize a variable, TS infers the type:

let name = "Ada";          // inferred: string
let age = 36;               // inferred: number
let admin = true;           // inferred: boolean

The redundant annotations would just clutter:

let name: string = "Ada";   // ✗ unnecessary
let name = "Ada";           // ✓ better

Required-ish: Empty Initializers

let names: string[] = [];   // type otherwise becomes `never[]` / `any[]`

let user: User | null = null;

When the initial value doesn’t reflect the eventual type, annotate.

Annotations as Contracts

For exported / public functions, annotating returns is good discipline — it documents intent AND prevents accidental changes:

export function fetchUser(id: string): Promise<User> {
  return fetch(`/api/users/${id}`).then(r => r.json());
}

If you accidentally make this return something else, the compiler flags it. Without the annotation, the implementation defines the contract.

Wide vs Narrow Inference

let variables infer wide; const variables infer narrow:

let kind = "admin";       // string
const kind = "admin";     // "admin"  (literal type)

We come back to this in the literal types lesson.

Rule of Thumb

  • Parameters: ALWAYS annotate
  • Exported function returns: annotate (documentation)
  • Local variables with literal initializers: don’t annotate
  • Empty arrays / null initial states: annotate
  • Type-inferred everything else: don’t annotate

Up Next

The flip side — what TypeScript can infer for free.

Inference →