Type Annotations in TypeScript
A type annotation tells TypeScript exactly what type a variable, parameter, or return value should be. Learn the syntax, where annotations go, and when to use them.
Type annotations in TypeScript are the : type syntax you write after a variable, function parameter, or function signature. An annotation tells the compiler exactly what type a value should be, and the compiler rejects any assignment that does not match.
Here is the smallest useful example:
let message: string = "Hello, TypeScript!";
console.log(message.toUpperCase());The colon and string after message set a contract: this variable must always hold text. Try assigning it a number instead, and TypeScript refuses to compile the file until you fix the mismatch.
let message: string = "Hello, TypeScript!";
message = 42;TypeScript compares the new value against the original annotation on every assignment, not just the first one. Because 42 is a number and message was declared as a string, the compiler stops here and reports the exact mismatch before the code ever runs.
Type 'number' is not assignable to type 'string'.Plain JavaScript would accept that reassignment silently and only fail later, possibly deep inside a function that expected a string. This is the core value of annotations: they turn a mistake into a compile-time error instead of a runtime surprise.
Where Annotations Go
Type annotations appear in three main positions, and each one protects a different part of your code.
Variable Annotations
Place the annotation after the variable name:
let count: number = 0;
let name: string = "Alice";
let isReady: boolean = false;Each annotation sets a contract for that variable, exactly like the message example above. TypeScript checks every future assignment against it and rejects anything that does not match, whether that happens on the next line or two hundred lines later.
Function Parameter Annotations
Place the annotation after each parameter name:
function greet(name: string, age: number) {
return `${name} is ${age} years old.`;
}
console.log(greet("Alice", 30));Parameters are the most important place to add annotations. TypeScript has no value to look at when a function is declared, so unlike variables, it cannot infer a parameter's type on its own. Without an annotation, a parameter falls back to any and loses all type safety, which means the compiler stops catching mistakes at that call site entirely.
greet("Alice", "thirty");Because age is annotated as a number, passing a string in that position is a type mismatch. TypeScript flags this call before the code runs, right where the mistake happens, rather than letting it become a bug you have to trace later.
Argument of type 'string' is not assignable to parameter of type 'number'.Return Type Annotations
A return type annotation protects the output side of a function instead of its inputs. Place it after the parameter list, before the function body:
function add(a: number, b: number): number {
return a + b;
}A return type annotation does two things: it documents what callers should expect, and it forces the compiler to check that every path through the function body actually returns that type. The function below forgets to return a value when the condition is false.
function getScore(): number {
if (Math.random() > 0.5) {
return 100;
}
}Because the return type is annotated as a number, TypeScript checks every code path and notices that the false branch falls through without returning anything at all. It reports the missing return immediately instead of letting the bug surface later as undefined in production.
Function lacks ending return statement and return type does not include 'undefined'.Without the return type annotation, TypeScript would have silently widened the inferred type instead of raising an error, and the missing return could slip through unnoticed. For more on how TypeScript figures out types without annotations, see type inference in TypeScript.
Common Annotation Types
Annotations are not limited to the three primitives shown above. The same colon-and-type pattern works for arrays, objects, and unions of multiple types:
let scores: number[] = [85, 92, 78];
let user: { name: string; age: number } = {
name: "Alice",
age: 30,
};
let id: string | number = "abc123";scores only accepts an array of numbers, user requires both a name and an age with the listed types, and id accepts either a string or a number because of the union. The syntax stays identical no matter how complex the type gets: a colon, then the type.
When to Annotate and When to Skip
You do not need to annotate every variable. TypeScript infers types from the initial value in most cases, so writing an annotation on top of an obvious value just adds noise:
let title = "Learn TypeScript"; // TypeScript infers string
let count = 0; // TypeScript infers numberAnnotations are most useful in these situations:
- Function parameters: TypeScript cannot infer them, so you must annotate.
- Function return types: they catch mistakes inside the function and document intent.
- Variables declared without an initial value: TypeScript cannot infer a type with nothing to look at.
- When you want a narrower type than inference gives you: a union instead of a wider primitive, for example.
For a deeper look at when inference is enough and when you should add annotations, read when to add type annotations in TypeScript.
Common Mistakes
Forgetting Parameter Annotations
The most common beginner mistake is skipping annotations on function parameters:
function greet(name, age) {
return `${name} is ${age}`;
}Without annotations, both name and age become any, so the function accepts anything you pass it, including values that would crash the template string. Enabling strict mode in your compiler configuration turns this into a hard error instead of a silent risk.
Annotating Variables That Do Not Need It
let color: string = "blue";
let items: number[] = [1, 2, 3];TypeScript already knows the type of each variable from its initial value, so these annotations repeat information the compiler already has. Removing them keeps the code shorter without losing any safety.
Forgetting That Annotations Are Compile-Time Only
let value: number = 42;
console.log(typeof value); // "number"Type annotations do not exist once the code runs. You cannot inspect an annotation with typeof, and it has zero effect on execution; after compilation the annotation is gone, leaving plain JavaScript behind with no trace of the type. This distinction between compile-time types and runtime values comes up often, especially when working with APIs, user input, and validation.
Rune AI
Key Insights
- A type annotation uses
: typesyntax after a variable, parameter, or function signature. - Annotations are removed during compilation and have zero runtime effect.
- Function parameters always need annotations because TypeScript cannot infer them.
- Return type annotations catch mistakes inside the function body, not just at call sites.
- Let TypeScript infer types where it can, and annotate where inference is not enough.
Frequently Asked Questions
Do type annotations affect runtime performance?
Do I need to annotate every variable?
What happens if I annotate a variable with the wrong type?
Conclusion
Type annotations are the core syntax that makes TypeScript useful. By writing : type after a variable, parameter, or function, you tell the compiler what to expect. Annotations catch mistakes at compile time, document your intent for teammates, and power editor features like autocomplete and refactoring. Start with annotations on function parameters and return types, let inference handle the rest, and add more annotations as your codebase grows.
More in this topic
TypeScript Compiler Explained
The TypeScript compiler (tsc) turns .ts files into .js files while checking types. Learn what it does, how to use it, and how it fits your workflow.
Set Up ESLint for TypeScript
Install and configure ESLint with typescript-eslint to catch bugs and enforce consistent code style in your TypeScript project.
Pick Utility Type in TypeScript
Pick creates a new type by selecting only the properties you name from an existing type. Use it to build focused types for specific views, API responses, or function parameters.