TypeScript
01 / 18

Type System & Annotations

TypeScript Type System & Annotations

TypeScript extends JavaScript by adding static type definitions. Types provide a way to describe the shape of objects, enabling better documentation and error detection.

Basic Types

// Primitives
let name: string = 'John';
let age: number = 30;
let isActive: boolean = true;
let nothing: null = null;
let notDefined: undefined = undefined;

// BigInt and Symbol
let bigNumber: bigint = 100n;
let uniqueId: symbol = Symbol('id');

// Arrays
let numbers: number[] = [1, 2, 3];
let strings: Array<string> = ['a', 'b', 'c'];

// Tuples - fixed length and types
let tuple: [string, number] = ['age', 30];
let RGB: [number, number, number] = [255, 0, 0];

// Tuple with optional and rest
let optional: [string, number?] = ['hello'];
let rest: [string, ...number[]] = ['numbers', 1, 2, 3];

// Objects
let user: { name: string; age: number } = {
  name: 'John',
  age: 30
};

// Optional properties
let person: { name: string; age?: number } = { name: 'Jane' };

// Readonly properties
let readonlyUser: { readonly id: number; name: string } = {
  id: 1,
  name: 'John'
};
// readonlyUser.id = 2;  // Error!

// Index signatures
interface StringMap {
  [key: string]: string;
}

const map: StringMap = {
  name: 'John',
  email: 'john@example.com'
};

Function Types

// Function declarations
function add(a: number, b: number): number {
  return a + b;
}

// Arrow functions
const multiply = (a: number, b: number): number => a * b;

// Optional parameters
function greet(name: string, greeting?: string): string {
  return `${greeting || 'Hello'}, ${name}`;
}

// Default parameters
function createUser(name: string, age: number = 18): User {
  return { name, age };
}

// Rest parameters
function sum(...numbers: number[]): number {
  return numbers.reduce((acc, n) => acc + n, 0);
}

// Function type
type MathOperation = (a: number, b: number) => number;

const divide: MathOperation = (a, b) => a / b;

// Void return type
function log(message: string): void {
  console.log(message);
}

// Never return type (never returns)
function throwError(message: string): never {
  throw new Error(message);
}

function infiniteLoop(): never {
  while (true) {}
}

// Function overloads
function getValue(value: string): string;
function getValue(value: number): number;
function getValue(value: boolean): boolean;
function getValue(value: string | number | boolean): string | number | boolean {
  return value;
}

const str = getValue('hello');  // Type: string
const num = getValue(42);       // Type: number

Union & Intersection Types

// Union types (OR)
let id: string | number;
id = 'abc';  // ✅
id = 123;    // ✅
// id = true;   // ❌ Error

type Status = 'pending' | 'active' | 'completed';
let status: Status = 'pending';  // ✅
// status = 'invalid';  // ❌ Error

// Intersection types (AND)
type Person = {
  name: string;
  age: number;
};

type Employee = {
  company: string;
  salary: number;
};

type EmployedPerson = Person & Employee;

const employee: EmployedPerson = {
  name: 'John',
  age: 30,
  company: 'ACME',
  salary: 50000
};

// Discriminated unions
type Circle = {
  kind: 'circle';
  radius: number;
};

type Rectangle = {
  kind: 'rectangle';
  width: number;
  height: number;
};

type Shape = Circle | Rectangle;

function getArea(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2;
    case 'rectangle':
      return shape.width * shape.height;
  }
}

Type Aliases & Interfaces

// Type alias
type Point = {
  x: number;
  y: number;
};

type ID = string | number;

// Interface
interface User {
  id: number;
  name: string;
  email: string;
  age?: number;  // Optional
  readonly createdAt: Date;  // Readonly
}

const user: User = {
  id: 1,
  name: 'John',
  email: 'john@example.com',
  createdAt: new Date()
};

// Interface extension
interface Admin extends User {
  role: 'admin' | 'superadmin';
  permissions: string[];
}

const admin: Admin = {
  id: 1,
  name: 'Jane',
  email: 'jane@example.com',
  createdAt: new Date(),
  role: 'admin',
  permissions: ['read', 'write']
};

// Interface merging (declaration merging)
interface Window {
  myCustomProperty: string;
}

interface Window {
  myOtherProperty: number;
}

// Now Window has both properties

// Type vs Interface:
// - Interface: can be extended, supports declaration merging
// - Type: more flexible, can use unions/intersections
// - Use interface for object shapes, type for everything else

Type Assertions & Type Guards

// Type assertion
const value: any = 'hello';
const length1 = (value as string).length;
const length2 = (<string>value).length;  // Alternative syntax

// Non-null assertion
function getValue(id: string | null): string {
  return id!.toUpperCase();  // Asserts id is not null
}

// Type guards
function isString(value: unknown): value is string {
  return typeof value === 'string';
}

function processValue(value: string | number) {
  if (isString(value)) {
    console.log(value.toUpperCase());  // TypeScript knows it's string
  } else {
    console.log(value.toFixed(2));  // TypeScript knows it's number
  }
}

// typeof guard
function example(value: string | number) {
  if (typeof value === 'string') {
    return value.toUpperCase();
  }
  return value.toFixed(2);
}

// instanceof guard
class Dog {
  bark() { console.log('Woof!'); }
}

class Cat {
  meow() { console.log('Meow!'); }
}

function makeSound(animal: Dog | Cat) {
  if (animal instanceof Dog) {
    animal.bark();
  } else {
    animal.meow();
  }
}

// in operator
type Fish = { swim: () => void };
type Bird = { fly: () => void };

function move(animal: Fish | Bird) {
  if ('swim' in animal) {
    animal.swim();
  } else {
    animal.fly();
  }
}

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free