TypeScript Mastery Guide

Elevate your JavaScript with static types and modern tooling

npm install -g typescript

What is TypeScript?

TypeScript is a strongly typed superset of JavaScript that compiles to plain JavaScript. Developed and maintained by Microsoft, it adds optional static typing and class-based object-oriented programming to JavaScript.

Why TypeScript?

  • Catch errors during development rather than runtime
  • Enhanced code editor support with IntelliSense
  • Better documentation through type definitions
  • Easier refactoring of large codebases

Key Features

Static Typing Interfaces Classes Generics Decorators Modules ES6+ Support

Basic Types

Type Description Example
string Textual data let name: string = "Alice";
number Numeric values (int/float) let age: number = 30;
boolean True/false values let isActive: boolean = true;
any Opt-out of type checking let dynamic: any = "could be anything";
unknown Type-safe counterpart of any let notSure: unknown = 4;
void Absence of any type (functions) function log(): void { console.log('hi'); }
never Values that never occur function error(message: string): never { throw new Error(message); }
// Type annotations with different basic types
let username: string = "typescript_user";
let userAge: number = 25;
let isAdmin: boolean = true;
let userData: any = "Can be anything";
let response: unknown = "Needs type checking";

// Arrays
let numbers: number[] = [1, 2, 3];
let names: Array<string> = ["Alice", "Bob"];

// Tuples - fixed length arrays
let user: [string, number, boolean] = ["Alice", 30, true];

Functions in TypeScript

TypeScript enhances JavaScript functions with parameter types, return types, and function overloading.

Basic Function

// Function with typed parameters and return type
function add(x: number, y: number): number {
  return x + y;
}

// Arrow function with types
const multiply = (a: number, b: number): number => a * b;

Optional & Default Parameters

// Optional parameter (with ?)
function greet(name: string, title?: string): string {
  return title ? `Hello, ${title} ${name}!` : `Hello, ${name}!`;
}

// Default parameter
function createUser(
  name: string,
  role: string = "user"
): void {
  console.log(`Created ${name} with role ${role}`);
}

Function Overloading

// Function overload signatures
function getData(id: number): string;
function getData(name: string): string[];

// Implementation
function getData(input: number | string): string | string[] {
  if (typeof input === "number") {
    return `Data for ID ${input}`;
  } else {
    return ["Data 1", "Data 2"];
  }
}

Interfaces & Type Aliases

Interfaces

Interfaces define contracts for object shapes and can be extended.

// Basic interface
interface User {
  id: number;
  name: string;
  email?: string; // Optional property
}

// Using the interface
const currentUser: User = {
  id: 1,
  name: "Alice"
};

// Extending interfaces
interface Admin extends User {
  privileges: string[];
}

Type Aliases

Type aliases can represent primitives, unions, tuples, and more.

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

// Union types
type ID = number | string;

// Tuple type
type Data = [string, number];

// Function type
type GreetFunction = (name: string) => string;

// Intersection types
type AdminUser = User & { isAdmin: true };

Interface vs Type

Feature Interface Type
Extending extends keyword Intersection (&)
Implementing Can be implemented by class Cannot be implemented
Declaration merging Multiple declarations merge Cannot be re-declared
Primitives/unions/tuples No Yes

Classes in TypeScript

TypeScript adds type annotations and visibility modifiers to ES6 classes.

Basic Class

class Person {
  // Properties with types
  name: string;
  age: number;

  // Constructor with parameter properties
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  // Method with return type
  describe(): string {
    return `${this.name} is ${this.age} years old.`;
  }
}

// Usage
const person = new Person("Alice", 30);
console.log(person.describe());

Access Modifiers

class Employee {
  // Public by default
  public name: string;

  // Private - only accessible within class
  private salary: number;

  // Protected - accessible in subclasses
  protected department: string;

  constructor(name: string, salary: number, department: string) {
    this.name = name;
    this.salary = salary;
    this.department = department;
  }

  // Readonly - can't be modified after initialization
  readonly id: number = Math.floor(Math.random() * 1000);
}

Inheritance & Interfaces

interface ClockInterface {
  currentTime: Date;
  setTime(d: Date): void;
}

class Clock implements ClockInterface {
  currentTime: Date = new Date();
  setTime(d: Date) {
    this.currentTime = d;
  }
  constructor(public hour: number, public minute: number) {}
}

class DigitalClock extends Clock {
  display(): string {
    return `${this.hour}:${this.minute}`;
  }
}

Generics

Generics allow creating reusable components that work with multiple types while maintaining type safety.

Generic Functions

// Simple generic function
function identity<T>(arg: T): T {
  return arg;
}

// Usage with explicit type
let output1 = identity<string>("Hello");
// Type inference
let output2 = identity(42);

// Generic with multiple types
function merge<T, U>(obj1: T, obj2: U): T & U {
  return { ...obj1, ...obj2 };
}

const merged = merge({ name: "Alice" }, { age: 30 });

Generic Constraints

// Constraint with interface
interface Lengthwise {
  length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}

// Now works with arrays and strings
loggingIdentity([1, 2, 3]);
loggingIdentity("hello");

// Keyof constraint
function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

Generic Classes

class GenericNumber<T> {
  zeroValue: T;
  add: (x: T, y: T) => T;

  constructor(zeroValue: T, add: (x: T, y: T) => T) {
    this.zeroValue = zeroValue;
    this.add = add;
  }
}

// Usage with number
let myGenericNumber = new GenericNumber<number>(0, (x, y) => x + y);

// Usage with string
let stringNumeric = new GenericNumber<string>("", (x, y) => x + y);

Advanced Types

Union & Intersection Types

// Union type - value can be one of several types
function printId(id: number | string) {
  console.log(`ID: ${id}`);
}

// Type guards with typeof
function printIdSafe(id: number | string) {
  if (typeof id === "string") {
    console.log(id.toUpperCase());
  } else {
    console.log(id.toFixed(2));
  }
}

// Intersection type - combines multiple types
type Admin = {
  name: string;
  privileges: string[];
};

type Employee = {
  name: string;
  startDate: Date;
};

type ElevatedEmployee = Admin & Employee;

Type Guards & Discriminated Unions

// Type guard with 'in' operator
interface Bird {
  fly(): void;
  layEggs(): void;
}

interface Fish {
  swim(): void;
  layEggs(): void;
}

function getSmallPet(): Fish | Bird {
  return Math.random() > 0.5 ? { fly: () => {}, layEggs: () => {} } : { swim: () => {}, layEggs: () => {} };
}

function move(pet: Fish | Bird) {
  if ("swim" in pet) {
    return pet.swim();
  }
  return pet.fly();
}

// Discriminated union with 'kind' property
interface Square {
  kind: "square";
  size: number;
}

interface Circle {
  kind: "circle";
  radius: number;
}

type Shape = Square | Circle;

function area(shape: Shape): number {
  switch (shape.kind) {
    case "square": return shape.size * shape.size;
    case "circle": return Math.PI * shape.radius ** 2;
  }
}

Utility Types

TypeScript provides several utility types to facilitate common type transformations.

Utility Type Description Example
Partial<T> Makes all properties in T optional Partial<User>
Required<T> Makes all properties in T required Required<User>
Readonly<T> Makes all properties in T readonly Readonly<User>
Record<K,T> Constructs an object type with property keys of type K and values of type T Record<string, number>
Pick<T,K> Picks a set of properties K from T Pick<User, "name" | "email">
Omit<T,K> Omits a set of properties K from T Omit<User, "password">
Exclude<T,U> Excludes from T those types that are assignable to U Exclude<string | number, number>

Decorators (Experimental)

Decorators provide a way to add both annotations and a meta-programming syntax for class declarations and members.

Class Decorator

// Simple decorator that logs class construction
function logged(constructor: Function) {
  console.log(`Class ${constructor.name} was defined`);
}

// Applying the decorator
@logged
class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
  greet() {
    return `Hello, ${this.greeting}`;
  }
}

Method Decorator

// Decorator factory that logs method calls
function logMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;

  // Replace the original method with a wrapper
  descriptor.value = function (...args: any[]) {
    console.log(`Calling ${propertyKey} with args: ${JSON.stringify(args)}`);
    const result = originalMethod.apply(this, args);
    console.log(`Method ${propertyKey} returned: ${JSON.stringify(result)}`);
    return result;
  };

  return descriptor;
}

class Calculator {
  @logMethod
  add(x: number, y: number): number {
    return x + y;
  }
}

Property & Parameter Decorators

// Property decorator that adds metadata
function format(formatString: string) {
  return function (target: any, propertyKey: string) {
    console.log(`Property ${propertyKey} will be formatted with: ${formatString}`);
  };
}

// Parameter decorator
function validate(target: any, propertyKey: string, parameterIndex: number) {
  console.log(`Validating parameter ${parameterIndex} of ${propertyKey}`);
}

class Greeter {
  @format("Hello, %s")
  greeting: string;

  constructor(message: string) {
    this.greeting = message;
  }

  greet(@validate name: string) {
    return `${this.greeting}, ${name}`;
  }
}

Configuration & Tooling

tsconfig.json

The tsconfig.json file specifies the root files and the compiler options required to compile the project.

// Example tsconfig.json
{
  "compilerOptions": {
    "target": "ES6",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

Important Compiler Options

Strict Type-Checking

strict noImplicitAny strictNullChecks strictFunctionTypes

Module Resolution

module moduleResolution esModuleInterop allowSyntheticDefaultImports

Source Maps & Output

<