Course

TypeScript Annotations: Syntax, Usage, and Examples

TypeScript annotations let you define the types of variables, function parameters, return values, and objects in your code. They help you catch errors early and make your code easier to read and maintain.

What Are TypeScript Annotations?

TypeScript annotations specify data types in your code, ensuring values match their expected types. These annotations do not affect the compiled JavaScript but help you write more reliable code.

tsx
let message: string = "Hello, TypeScript!"; let count: number = 10;

Here, message must always hold a string, and count must always be a number.

Type Annotations Can Only Be Used in TypeScript

Type annotations exist only in TypeScript. JavaScript does not support them, so when you compile your TypeScript code, the TypeScript compiler removes them.

How to Use TypeScript Annotations

Use Type Annotations with Variables

You can define types for different values:

tsx
let isDone: boolean = false; let amount: number = 42; let userName: string = "Alice"; let hobbies: string[] = ["Reading", "Gaming"];

Add Type Annotations to Functions

Type annotations help prevent incorrect arguments and return values.

tsx
function add(a: number, b: number): number { return a + b; } function greet(name: string): string { return `Hello, ${name}!`; }

If you try passing a string to add(), TypeScript will throw an error.

Define Object Type Annotations

Use type annotations to describe objects:

tsx
type User = { name: string; age: number; isActive: boolean; }; let user: User = { name: "John", age: 30, isActive: true, };

This ensures that every User object has the correct structure.

When Should You Use TypeScript Annotations?

Prevent Errors Before They Happen

Type annotations stop you from assigning incorrect values:

tsx
let total: number; total = "100"; // Error: Type 'string' is not assignable to type 'number'.

Make Your Code Easier to Read

Annotations clarify what data your functions expect and return.

tsx
function multiply(a: number, b: number): number { return a * b; }

Anyone reading this function knows it takes two numbers and returns a number.

Create Custom Types

Define reusable types to keep your code consistent:

tsx
interface Product { id: number; name: string; price: number; } let item: Product = { id: 1, name: "Laptop", price: 999.99 };

If you try adding a category field, TypeScript will warn you.

Examples of TypeScript Annotations

Use Type Annotations with Arrays

tsx
let numbers: number[] = [1, 2, 3, 4, 5]; let users: string[] = ["Alice", "Bob", "Charlie"];

Use Optional and Default Parameters

You can make function parameters optional or set default values:

tsx
function getFullName(firstName: string, lastName?: string): string { return lastName ? `${firstName} ${lastName}` : firstName; } console.log(getFullName("Alice")); // Alice console.log(getFullName("Alice", "Smith")); // Alice Smith

Use Generics to Keep Types Flexible

Generics let you use annotations while allowing different types:

tsx
function identity<T>(arg: T): T { return arg; } console.log(identity<string>("Hello")); // Hello console.log(identity<number>(42)); // 42

Specify Function Return Types

Explicit return types help avoid unintended results:

tsx
function getDiscount(price: number): number { return price * 0.1; }

Type Annotations vs. Decorators

Type annotations define variable and function types at compile time, while decorators modify runtime behavior.

tsx
function Log(target: any, propertyKey: string) { console.log(`${propertyKey} was accessed`); } class Example { @Log message: string = "Hello"; }

Use type annotations to enforce correctness and decorators to add metadata or change behavior dynamically.

TypeScript Custom Annotations

You can create custom types for structured data.

tsx
type ApiResponse<T> = { status: number; data: T; }; let response: ApiResponse<string> = { status: 200, data: "Success" };

Use the TypeScript Override Annotation

The override keyword ensures child classes correctly override methods from parent classes.

tsx
class Base { greet(): string { return "Hello from Base"; } } class Derived extends Base { override greet(): string { return "Hello from Derived"; } }

If greet() does not exist in Base, TypeScript will show an error.

Use the TypeScript Deprecated Annotation

Mark functions as deprecated using JSDoc comments.

tsx
class Legacy { /** * @deprecated Use newMethod() instead. */ oldMethod() { console.log("Deprecated method"); } newMethod() { console.log("New method"); } }

This warns developers to avoid using outdated methods.

TypeScript Variance Annotations

Variance annotations control how types relate in generics. TypeScript supports covariance (subtypes allowed) and contravariance (supertypes allowed).

Covariance Example

tsx
class Animal {} class Dog extends Animal {} let animals: Animal[] = [new Dog()]; // Allowed

Contravariance Example

tsx
type Callback = (arg: Dog) => void; let handler: Callback = (animal: Animal) => {}; // Allowed

Covariance lets you assign a subtype where a supertype is expected. Contravariance works in reverse.

Learn More About TypeScript Annotations

Let TypeScript Infer Types for Simplicity

TypeScript guesses types if you do not specify them.

tsx
let age = 25; // Inferred as number

For clarity, use explicit annotations when needed:

tsx
let score: number = 100;

Use Union and Intersection Types

Union types allow multiple types:

tsx
let value: string | number; value = "Hello"; // Valid value = 42; // Valid

Intersection types merge multiple types:

tsx
type Developer = { name: string }; type Engineer = { skills: string[] }; type DevOps = Developer & Engineer; let employee: DevOps = { name: "Alice", skills: ["Docker", "AWS"] };

Use Enums and Literal Types

Enums create named constants:

tsx
enum Status { Active, Inactive, Pending, } let currentStatus: Status = Status.Active;

Literal types restrict values:

tsx
let theme: "light" | "dark"; theme = "light"; // Valid theme = "blue"; // Error