Skip to main content

TypeScript Type Annotations

TypeScript's most powerful feature is its type system. Type annotations allow you to tell TypeScript what type of data a variable, parameter, or function return value should be. This creates a contract within your code that TypeScript will enforce during compilation.

Introduction

JavaScript is dynamically typed, which means variables can hold values of any type and change types during runtime. While this provides flexibility, it can lead to bugs that are difficult to track down. TypeScript solves this problem with static typing through type annotations.

Type annotations are a way to explicitly tell TypeScript what type a variable or expression should be, helping catch type-related errors before your code runs.

Basic Type Annotations

Syntax

In TypeScript, you add a type annotation by adding a colon (:) after the variable name, followed by the type.

typescript
let variableName: Type;

Primitive Types

TypeScript supports all JavaScript primitive types:

typescript
// String
let name: string = "John Doe";

// Number (includes integers, floats, etc.)
let age: number = 30;
let price: number = 19.99;

// Boolean
let isActive: boolean = true;

// Type inference works too!
let inferredString = "Hello"; // TypeScript knows this is a string

Type inference means TypeScript can often determine the type without an explicit annotation. However, it's good practice to add annotations for function parameters and return types.

Arrays and Special Types

Array Type Annotations

There are two ways to annotate arrays:

typescript
// Method 1: Type[]
let numbers: number[] = [1, 2, 3, 4, 5];
let names: string[] = ["Alice", "Bob", "Charlie"];

// Method 2: Array<Type>
let scores: Array<number> = [85, 90, 95];
let fruits: Array<string> = ["Apple", "Banana", "Orange"];

Any Type

The any type allows a variable to be of any type, effectively opting out of type checking:

typescript
let flexible: any = 4;
flexible = "A string";
flexible = { key: "value" };
// No errors! But be careful, this defeats the purpose of TypeScript

While any is useful in certain situations, it's best to avoid it when possible as it negates TypeScript's benefits.

Void Type

The void type is commonly used for functions that don't return a value:

typescript
function logMessage(message: string): void {
console.log(message);
// No return statement needed
}

Null and Undefined

TypeScript has types for JavaScript's null and undefined values:

typescript
let notDefined: undefined = undefined;
let absent: null = null;

Object Type Annotations

Object Literals

For simple objects, you can define their shape:

typescript
let person: { name: string; age: number } = {
name: "John",
age: 30
};

// With optional properties (?)
let user: { id: number; email: string; phone?: string } = {
id: 1,
email: "[email protected]"
// phone is optional
};

Function Type Annotations

Parameters and Return Types

TypeScript allows you to annotate both parameters and return values:

typescript
// Basic function with type annotations
function add(a: number, b: number): number {
return a + b;
}

// Arrow function with type annotations
const multiply = (x: number, y: number): number => {
return x * y;
};

Optional and Default Parameters

You can make parameters optional or provide default values:

typescript
// Optional parameter (note the ?)
function greet(name: string, greeting?: string): string {
if (greeting) {
return `${greeting}, ${name}!`;
}
return `Hello, ${name}!`;
}

// Default parameter
function welcome(name: string, message: string = "Welcome"): string {
return `${message}, ${name}!`;
}

greet("Alice"); // "Hello, Alice!"
greet("Bob", "Hi"); // "Hi, Bob!"
welcome("Charlie"); // "Welcome, Charlie!"

Union Types

Union types allow a variable to be one of several types:

typescript
// Can be either a string or a number
let id: string | number;
id = 101; // Valid
id = "ABC-123"; // Also valid
// id = true; // Error! Type 'boolean' is not assignable

// Example with a function
function formatId(id: string | number): string {
if (typeof id === "string") {
return id.toUpperCase();
}
return `ID-${id}`;
}

console.log(formatId("abc")); // "ABC"
console.log(formatId(123)); // "ID-123"

Type Aliases

Type aliases let you create custom names for types:

typescript
// Creating a type alias
type UserID = string | number;

function getUserById(id: UserID) {
// Implementation
}

// More complex example
type Point = {
x: number;
y: number;
};

function calculateDistance(p1: Point, p2: Point): number {
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
return Math.sqrt(dx * dx + dy * dy);
}

Real-World Example: Form Validation

Here's a practical example of how type annotations can help with form validation:

typescript
type FormField = {
value: string;
isValid: boolean;
errorMessage?: string;
};

type RegistrationForm = {
username: FormField;
email: FormField;
password: FormField;
confirmPassword: FormField;
};

function initForm(): RegistrationForm {
return {
username: { value: "", isValid: false },
email: { value: "", isValid: false },
password: { value: "", isValid: false },
confirmPassword: { value: "", isValid: false }
};
}

function validateEmail(email: string): boolean {
// Simple email validation
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

function updateField(form: RegistrationForm, field: keyof RegistrationForm, value: string): RegistrationForm {
const updatedForm = { ...form };

updatedForm[field].value = value;

// Validate based on field type
switch (field) {
case "email":
updatedForm.email.isValid = validateEmail(value);
updatedForm.email.errorMessage = updatedForm.email.isValid ?
undefined : "Please enter a valid email address";
break;
case "password":
updatedForm.password.isValid = value.length >= 8;
updatedForm.password.errorMessage = updatedForm.password.isValid ?
undefined : "Password must be at least 8 characters";
break;
// More validations for other fields...
}

return updatedForm;
}

// Usage
let form = initForm();
form = updateField(form, "email", "user@example");
console.log(form.email.isValid); // false
console.log(form.email.errorMessage); // "Please enter a valid email address"

form = updateField(form, "email", "[email protected]");
console.log(form.email.isValid); // true
console.log(form.email.errorMessage); // undefined

This example shows how type annotations can help you build robust form validation by:

  1. Clearly defining the shape of your form data
  2. Ensuring you only update valid form fields
  3. Providing type safety when accessing form properties
  4. Making validation errors more predictable

Type Annotations vs Type Assertions

It's important to understand the difference between type annotations and type assertions:

typescript
// Type annotation: tells TypeScript what the type should be
let someValue: string = "hello";

// Type assertion: tells TypeScript to treat a value as a specific type
let strLength: number = (someValue as string).length;
// Alternative syntax (less common)
let otherLength: number = (<string>someValue).length;

Type assertions don't change the runtime type - they just tell the TypeScript compiler to treat a value as having a specific type.

Type Annotations in the Real World

Let's explore a few more practical examples:

API Response Typing

typescript
type User = {
id: number;
name: string;
email: string;
createdAt: string;
};

type ApiResponse<T> = {
data: T;
status: number;
message: string;
};

async function fetchUser(id: number): Promise<ApiResponse<User>> {
const response = await fetch(`https://api.example.com/users/${id}`);
const data: ApiResponse<User> = await response.json();
return data;
}

// Usage
fetchUser(123).then(response => {
const user = response.data;
console.log(`Hello ${user.name}!`);
});

Configuration Objects

typescript
type DatabaseConfig = {
host: string;
port: number;
username: string;
password: string;
database: string;
ssl?: boolean;
timeout?: number;
};

function connectToDatabase(config: DatabaseConfig): void {
console.log(`Connecting to ${config.database} at ${config.host}:${config.port}`);
// Implementation would go here
}

connectToDatabase({
host: "localhost",
port: 5432,
username: "admin",
password: "secret",
database: "myapp",
ssl: true
});

Summary

Type annotations are at the heart of TypeScript, providing:

  1. Safety: Catch type errors during development, not at runtime
  2. IDE support: Better autocomplete and inline documentation
  3. Self-documentation: Code that clearly communicates intent
  4. Confidence: Make changes with less fear of breaking things

Key points to remember:

  • Use : followed by a type to annotate variables, parameters, and return values
  • TypeScript can infer many types automatically, but explicit annotations help with code clarity
  • Union types (|) let you specify multiple possible types
  • Type aliases help you create reusable, named types
  • Annotate function parameters and return types for better code documentation
  • Avoid using any when possible, as it defeats TypeScript's purpose

Practice Exercises

  1. Create a function called calculateArea that can calculate the area of either a circle or a rectangle, based on the parameters provided.
  2. Define a type for a Todo item with properties for id, title, completed status, and optional due date.
  3. Write a function that takes an array of mixed types (numbers and strings) and returns the sum of all numbers and the concatenation of all strings.
  4. Create a type for representing a playing card with suit and value properties.

Additional Resources

By mastering type annotations, you'll write more robust code and catch errors earlier in the development process. As you become more comfortable with TypeScript's type system, you'll discover even more powerful features like interfaces, generics, and advanced type operations.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)