Difficulty: Beginner
Explicit type annotations are the most direct way to tell TypeScript what type a value should be. You write them using a colon followed by the type after a variable name, function parameter, or before a function body (for return types). The syntax is `variableName: Type`, `(param: Type) => ...`, or `function name(): ReturnType`. These annotations serve as both constraints and documentation.
Variable annotations are placed between the variable name and the assignment operator: `let name: string = "Alice"`. While TypeScript can infer the type from the initial value, explicit annotations are useful when you want to declare a variable before assigning it, when you want a wider type than what would be inferred, or when working with complex types that benefit from clarity. For instance, `let items: string[] = []` makes the intended element type clear even though the array starts empty.
Function parameter annotations are essential because TypeScript generally cannot infer parameter types from the function definition alone. Every parameter in a function should have a type annotation: `function greet(name: string, age: number)`. Return type annotations come after the parameter list: `function greet(name: string): string`. While return types can usually be inferred, adding them explicitly catches bugs where a function accidentally returns the wrong type and serves as documentation for other developers.
Annotations can be as simple as primitive types or as complex as nested object types, union types, and generic types. The key principle is that annotations are a contract: they declare what a value must be, and TypeScript enforces that contract throughout your code. If you annotate a variable as `string`, any attempt to assign a number will be flagged as an error at compile time.
// Explicit annotations on variables
let username: string = "Alice";
let age: number = 30;
let isOnline: boolean = false;
// Useful when initializing empty structures
let tags: string[] = [];
let scores: Map<string, number> = new Map();
tags.push("typescript");
tags.push("programming");
scores.set("Alice", 95);
console.log(tags.join(", "));
console.log(scores.get("Alice"));
Annotations are especially useful for empty initializations where TypeScript cannot infer the element or value type. Without the annotation, `[]` would be inferred as `never[]`.
// Parameters must be annotated; return type is recommended
function formatCurrency(amount: number, currency: string): string {
return `${currency}${amount.toFixed(2)}`;
}
// Arrow function with annotations
const multiply = (a: number, b: number): number => a * b;
console.log(formatCurrency(29.99, "quot;));
console.log(formatCurrency(1500, "EUR "));
console.log(multiply(6, 7));
Function parameters always need annotations. Return types can be inferred but adding them improves readability and catches errors when refactoring.
// Inline object type annotation
function printUser(user: { name: string; age: number; email: string }): void {
console.log(`${user.name} (${user.age}) - ${user.email}`);
}
// Variable with object type
const config: { port: number; host: string; debug: boolean } = {
port: 3000,
host: "localhost",
debug: true
};
printUser({ name: "Bob", age: 25, email: "bob@example.com" });
console.log(`Server: ${config.host}:${config.port}`);
Object types are written as `{ key: Type; key: Type }`. For complex objects used in multiple places, consider using interfaces or type aliases instead of inline annotations.
Variable annotations, Parameter annotations, Return type annotations, Type syntax