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