TLDR
A type annotation tells TypeScript exactly what type a variable, function parameter, or return value should be. You write it using a colon after the name, like let age: number = 30. Annotations are removed when TypeScript compiles to JavaScript. They only exist to help you catch bugs during development.
What is a Type Annotation?
A type annotation is a label you attach to a variable or function. It tells TypeScript what kind of value is allowed there.
TypeScript can guess types in many situations on its own. But sometimes it cannot. And sometimes you want to be extra clear about what a variable should hold. That is when you write an annotation yourself.
The syntax is simple. You put a colon after the name, then write the type:
let age: number;
let name: string;
let isActive: boolean;
The Basic Syntax
The pattern for a type annotation is always the same:
name: type
You can add it to variables, constants, function parameters, and return values. Here is the syntax for each:
| Where | Syntax | Example |
|---|---|---|
| Variable | let name: type |
let score: number |
| Constant | const name: type = value |
const title: string = "Admin" |
| Function parameter | (param: type) |
(age: number) |
| Function return value | (): type |
(): string |
| Array | name: type[] |
let tags: string[] |
| Object | name: { key: type } |
let user: { name: string } |
How to Annotate Variables
The most common place to use annotations is on variables declared with let.
let username: string;
let score: number;
let isLoggedIn: boolean;
Here, the variables are declared but not given a value yet. TypeScript knows their types from the annotation alone.
Later, when you assign a value, TypeScript checks that it matches:
username = "Alice"; // OK
username = 42; // Error: Type 'number' is not assignable to type 'string'.
score = 100; // OK
score = "one hundred"; // Error: Type 'string' is not assignable to type 'number'.
You can also annotate and assign a value in one line:
let username: string = "Alice";
let score: number = 100;
let isLoggedIn: boolean = true;
Annotating constants
const variables cannot be reassigned. You can still annotate them, but it is often not needed because TypeScript infers the type from the value.
const MAX_RETRIES: number = 3; // Annotation added for clarity
const API_URL: string = "https://api.example.com";
const DEBUG: boolean = false;
How to Annotate Function Parameters
Function parameters are the most important place to add type annotations. Without them, TypeScript cannot check if you call the function with the wrong values.
function greet(name: string): void {
console.log("Hello, " + name);
}
greet("Alice"); // OK
greet(42); // Error: Argument of type 'number' is not assignable to parameter of type 'string'.
Each parameter gets its own annotation:
function add(a: number, b: number): number {
return a + b;
}
add(5, 10); // OK — returns 15
add(5, "10"); // Error: Argument of type 'string' is not assignable to parameter of type 'number'.
Multiple parameters with different types
function createUser(name: string, age: number, isAdmin: boolean): void {
console.log(name, age, isAdmin);
}
createUser("Alice", 30, true); // OK
createUser(30, "Alice", true); // Error: wrong order of types
How to Annotate Function Return Types
You write the return type after the closing parenthesis of the parameters, before the opening curly brace.
function getWelcomeMessage(name: string): string {
return "Welcome, " + name;
}
The : string after (name: string) tells TypeScript this function must return a string.
If your function returns the wrong type, TypeScript catches it:
function getAge(): number {
return "thirty";
// Error: Type 'string' is not assignable to type 'number'.
}
The void return type
Use void when a function does not return anything:
function logMessage(message: string): void {
console.log(message);
// No return statement needed
}
void tells TypeScript and other developers: "This function does something but gives nothing back."
Common return type annotations
| Return Type | When to Use |
|---|---|
string |
Function returns text |
number |
Function returns a numeric value |
boolean |
Function returns true or false |
void |
Function performs an action but returns nothing |
string[] |
Function returns an array of strings |
number[] |
Function returns an array of numbers |
How to Annotate Arrays
To annotate an array, write the type followed by []:
let names: string[] = ["Alice", "Bob", "Charlie"];
let scores: number[] = [95, 87, 72];
let flags: boolean[] = [true, false, true];
TypeScript checks both the array itself and every item inside it:
let names: string[] = ["Alice", "Bob"];
names.push(42);
// Error: Argument of type 'number' is not assignable to parameter of type 'string'.
names.push("David"); // OK
The alternative Array<type> syntax
TypeScript also supports a second syntax for arrays using angle brackets:
let names: Array<string> = ["Alice", "Bob"];
let scores: Array<number> = [95, 87];
Both styles do exactly the same thing. Most developers use type[] because it is shorter and easier to read.
How to Annotate Objects
To annotate an object, you write the shape of the object inside curly braces:
let user: { name: string; age: number; isAdmin: boolean };
user = { name: "Alice", age: 30, isAdmin: true }; // OK
user = { name: "Alice", age: 30 }; // Error: missing 'isAdmin'
TypeScript checks that the object has all the required properties with the right types.
Inline object annotation
You can write the annotation inline when you declare the variable:
let product: { id: number; title: string; price: number } = {
id: 1,
title: "TypeScript Book",
price: 29.99
};
Optional properties in objects
Add a ? after the property name to make it optional:
let profile: { username: string; bio?: string } = {
username: "alice_dev"
// bio is optional, so we can skip it
};
Tip: For reusable object shapes, use a
typealias orinterfaceinstead of writing the annotation inline every time. We cover those in later posts.
How to Annotate Union Types
Sometimes a variable can hold more than one type. Use a union type with the | symbol:
let id: string | number;
id = 101; // OK
id = "abc_101"; // OK
id = true; // Error: Type 'boolean' is not assignable to type 'string | number'.
You can use union types on function parameters too:
function printId(id: string | number): void {
console.log("ID:", id);
}
printId(1); // OK
printId("abc"); // OK
Note: We cover union types in detail in a later post. This is just a quick look at the annotation syntax.
How to Annotate with null and undefined
With strict: true, you need to be explicit when a value can be null or undefined.
let selectedItem: string | null = null; // Can be a string or null
let cachedValue: number | undefined; // Can be a number or undefined
Without the | null or | undefined, TypeScript will not let you assign those values:
let name: string = null;
// Error: Type 'null' is not assignable to type 'string'.
let name: string | null = null; // OK
What Happens to Annotations After Compilation?
Type annotations do not exist in JavaScript. When TypeScript compiles your code, it removes every annotation.
TypeScript (before compile):
let username: string = "Alice";
let score: number = 100;
function add(a: number, b: number): number {
return a + b;
}
JavaScript (after compile):
let username = "Alice";
let score = 100;
function add(a, b) {
return a + b;
}
All the : string, : number, and : boolean parts are stripped out. The output is clean JavaScript with no trace of the types.
This means type annotations have zero runtime cost. They only help you during development.
Annotation vs Inference: When to Write It Yourself
TypeScript can infer many types without annotations. So when should you write them yourself?
Write the annotation when:
- You declare a variable without assigning a value right away
- The return type of a function is not obvious
- TypeScript infers a type that is too broad for your needs
- You want to make the code easier to read for your team
Skip the annotation when:
- The type is obvious from the assigned value
- TypeScript can figure it out from context
// Skip annotation: value makes the type obvious
let name = "Alice"; // TypeScript infers: string
let count = 0; // TypeScript infers: number
let active = true; // TypeScript infers: boolean
// Write annotation: no value assigned yet
let username: string;
let retries: number;
// Write annotation: return type is not obvious from function name
function fetchData(): Promise<string> {
return fetch("/api").then(res => res.text());
}
// Write annotation: function parameter always needs one
function double(n: number): number {
return n * 2;
}
Full Example Putting It All Together
Here is a small program that uses type annotations throughout:
// Variable annotations
let appName: string = "TaskManager";
let version: number = 1;
let isProduction: boolean = false;
// Array annotation
let taskTitles: string[] = ["Write blog", "Review PR", "Fix bug"];
// Object annotation
let currentUser: { id: number; name: string; email: string } = {
id: 1,
name: "Alice",
email: "alice@example.com"
};
// Function with parameter and return type annotations
function getTaskCount(tasks: string[]): number {
return tasks.length;
}
// Function with void return type
function logStatus(status: string): void {
console.log("App status:", status);
}
// Union type annotation
let searchInput: string | number = "";
// Optional value with null
let activeTask: string | null = null;
// Using the annotated values
const count: number = getTaskCount(taskTitles);
logStatus("running");
console.log(`${appName} v${version} — ${count} tasks loaded`);
Common Annotation Mistakes
Mistake 1: Using uppercase type names
let name: String = "Alice"; // Wrong — String is an object, not a primitive
let name: string = "Alice"; // Correct
Mistake 2: Forgetting the return type on public functions
// Unclear what this returns
function getUser(id: number) {
return { id, name: "Alice" };
}
// Clearer with a return type
function getUser(id: number): { id: number; name: string } {
return { id, name: "Alice" };
}
Mistake 3: Annotating when TypeScript can already infer the type
// Redundant annotation
let score: number = 100;
// Cleaner: TypeScript infers number from the value
let score = 100;
Mistake 4: Missing annotation on function parameters
// Bad: TypeScript has to guess what 'x' is
function square(x) {
return x * x;
}
// Good: annotation makes the intent clear
function square(x: number): number {
return x * x;
}
FAQ
Q: Do I have to annotate every variable?
No. TypeScript can infer many types on its own. Annotate when TypeScript cannot figure out the type, or when you want to make your intent clear to other developers.
Q: Do type annotations affect runtime performance?
No. TypeScript removes all annotations during compilation. The output is plain JavaScript with no types. Annotations have zero impact on how fast your code runs.
Q: What is the difference between a type annotation and a type assertion?
A type annotation tells TypeScript what type something should be. A type assertion (as SomeType) forces TypeScript to treat a value as a specific type, even if it disagrees. Annotations are the safe, standard way. Assertions should be used with care.
Q: Can I use type annotations in JavaScript files?
No. Type annotations only work in .ts files. If you need type hints in .js files, you can use JSDoc comments, but that is a different approach.
Q: Should I annotate the return type of every function?
It is good practice for public functions and exported utilities. For small internal helper functions where the return type is obvious, you can let TypeScript infer it.
Q: What happens if I annotate the wrong type?
TypeScript shows a compile error. For example, if you annotate a variable as number but assign a string, you will see an error immediately. The code will not compile until the mismatch is fixed.
What You Learned
- Type annotations use the
: typesyntax after a variable, parameter, or function name - You can annotate variables, constants, function parameters, return types, arrays, and objects
- Annotations are removed during compilation and have no runtime cost
- Use
voidfor functions that do not return a value - Use
type[]to annotate arrays - Use
{ key: type }to annotate objects inline - Use
|to annotate union types when a value can be more than one type - Write annotations when TypeScript cannot infer the type or when you want to be explicit
Sources: TypeScript Handbook — Type Annotations | TypeScript Tutorial — Type Annotations
Top comments (0)