TypeScript Guide

TypeScript Essential Guide

Learn TypeScript fundamentals, types, interfaces, generics, and advanced patterns for building robust, type-safe applications.

10
Sections
30+
Code Examples
5
Core Type Areas
4
Difficulty Levels

TypeScript Basics

Core syntax, type annotations, and inference

Beginner
2 examples

Type Annotations & Inference TS

Add explicit types and leverage TypeScript's type inference

// Basic annotations
let count: number = 0;
let title: string = 'Hello TS';
let isDone: boolean = false;

// Type inference (TS infers the type)
const maxItems = 10; // number
let message = 'Hi';  // string

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

// Object type
const user: { id: number; name: string } = {
  id: 1,
  name: 'Alice'
};

Basic Types TS

Use primitive types, any, unknown, and never

let id: number;
let username: string;
let isActive: boolean;
let nullable: string | null;
let undef: string | undefined;

// any (avoid when possible)
let anything: any;

// unknown (safer than any)
let value: unknown;
if (typeof value === 'string') {
  // value is string here
  console.log(value.toUpperCase());
}

// never (function that never returns)
function fail(message: string): never {
  throw new Error(message);
}

Interfaces & Type Aliases

Model object shapes with interfaces and type aliases

Beginner
2 examples

Interfaces TS

Define contracts for objects and extend them

interface User {
  id: number;
  name: string;
  email?: string;        // optional
  readonly createdAt: Date;
}

const user: User = {
  id: 1,
  name: 'Alice',
  createdAt: new Date()
};

// user.createdAt = new Date(); // Error: readonly

interface Admin extends User {
  permissions: string[];
}

const admin: Admin = {
  id: 2,
  name: 'Bob',
  createdAt: new Date(),
  permissions: ['read', 'write']
};

Type Aliases & Unions TS

Create reusable aliases and union types

type ID = number | string;

type UserRole = 'user' | 'admin' | 'editor';

type UserBase = {
  id: ID;
  name: string;
};

type AdminUser = UserBase & {
  role: 'admin';
  permissions: string[];
};

const adminUser: AdminUser = {
  id: 'a-1',
  name: 'Alice',
  role: 'admin',
  permissions: ['manage_users']
};

Functions & Parameters

Typed functions, parameters, and callbacks

Intermediate
2 examples

Typed Functions & Optional Parameters TS

Add types to parameters, return values, and optional arguments

function greet(name: string, greeting: string = 'Hello'): string {
  return greeting + ', ' + name;
}

function log(message: string, userId?: number): void {
  console.log(message, userId);
}

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

Function Types & Callbacks TS

Describe functions with types and use them as values

type Comparator<T> = (a: T, b: T) => number;

function sortBy<T>(items: T[], compare: Comparator<T>): T[] {
  return [...items].sort(compare);
}

const numbers = [3, 1, 2];

const sorted = sortBy(numbers, (a, b) => a - b);

Union & Intersection Types

Model flexible shapes and combine multiple types

Intermediate
2 examples

Union Types & Narrowing TS

Use unions with control-flow based type narrowing

type Result = string | number;

function formatResult(result: Result): string {
  if (typeof result === 'number') {
    return 'Result: ' + result.toFixed(2);
  }
  return 'Result: ' + result.toUpperCase();
}

// Discriminated union
interface LoadingState {
  status: 'loading';
}

interface SuccessState {
  status: 'success';
  data: string[];
}

interface ErrorState {
  status: 'error';
  error: string;
}

type State = LoadingState | SuccessState | ErrorState;

function handleState(state: State): void {
  switch (state.status) {
    case 'loading':
      console.log('Loading...');
      break;
    case 'success':
      console.log('Items:', state.data);
      break;
    case 'error':
      console.error(state.error);
      break;
  }
}

Intersection Types TS

Combine multiple types into one

type WithTimestamp = {
  createdAt: Date;
  updatedAt: Date;
};

type Post = {
  id: number;
  title: string;
};

type PostWithMeta = Post & WithTimestamp;

const post: PostWithMeta = {
  id: 1,
  title: 'Hello TS',
  createdAt: new Date(),
  updatedAt: new Date()
};

Generics

Reusable, type-safe components and functions

Intermediate
2 examples

Generic Functions TS

Write functions that work with multiple types

function identity<T>(value: T): T {
  return value;
}

const num = identity<number>(42);
const str = identity('hello'); // type inferred as string

function wrapInArray<T>(value: T): T[] {
  return [value];
}

const names = wrapInArray('Alice'); // string[]

Generic Interfaces & Constraints TS

Constrain generics with extends and reuse them in APIs

interface ApiResponse<T> {
  data: T;
  error?: string;
}

function fetchUser(): ApiResponse<{ id: number; name: string }> {
  return {
    data: { id: 1, name: 'Alice' }
  };
}

interface HasId {
  id: number;
}

function findById<T extends HasId>(items: T[], id: number): T | undefined {
  return items.find(item => item.id === id);
}

Classes & OOP

Use classes, access modifiers, and interfaces

Intermediate
2 examples

Classes & Access Modifiers TS

Define classes with public, private, and protected members

class Person {
  // access modifiers: public (default), private, protected
  constructor(
    public id: number,
    public name: string,
    private email: string
  ) {}

  greet(): string {
    return 'Hello, I am ' + this.name;
  }

  protected getEmail(): string {
    return this.email;
  }
}

const person = new Person(1, 'Alice', 'alice@example.com');
console.log(person.greet());
// person.email; // Error: private property

Implementing Interfaces & Repositories TS

Use interfaces to describe class contracts

interface Repository<T> {
  getById(id: number): T | undefined;
  getAll(): T[];
}

class InMemoryRepository<T extends { id: number }> implements Repository<T> {
  private items = new Map<number, T>();

  add(item: T): void {
    this.items.set(item.id, item);
  }

  getById(id: number): T | undefined {
    return this.items.get(id);
  }

  getAll(): T[] {
    return Array.from(this.items.values());
  }
}

Utility & Mapped Types

Leverage built-in helpers and create mapped types

Advanced
2 examples

Common Utility Types TS

Use Partial, Readonly, Pick, and Omit

interface User {
  id: number;
  name: string;
  email: string;
  isAdmin: boolean;
}

// Make all fields optional
type UserUpdate = Partial<User>;

// Read-only version
type ReadonlyUser = Readonly<User>;

// Pick subset of fields
type PublicUser = Pick<User, 'id' | 'name'>;

// Omit specific fields
type UserWithoutAdmin = Omit<User, 'isAdmin'>;

Mapped Types & Record TS

Create flexible mappings over keys

type Role = 'user' | 'admin' | 'editor';

// Map roles to permissions
type RolePermissions = Record<Role, string[]>;

const permissions: RolePermissions = {
  user: ['read'],
  admin: ['read', 'write', 'delete'],
  editor: ['read', 'write']
};

// Custom mapped type
type Optional<T> = {
  [K in keyof T]?: T[K];
};

type OptionalUser = Optional<User>;

Advanced Type Patterns

Type guards, keyof, and indexed access types

Advanced
2 examples

Type Guards & Narrowing TS

Write custom type guards to refine unknown values

function isDate(value: unknown): value is Date {
  return value instanceof Date;
}

function printValue(value: string | number | Date): void {
  if (typeof value === 'string') {
    console.log('String:', value.toUpperCase());
  } else if (typeof value === 'number') {
    console.log('Number:', value.toFixed(2));
  } else if (isDate(value)) {
    console.log('Date:', value.toISOString());
  }
}

keyof & Indexed Access Types TS

Reference property names and types in a type-safe way

interface User {
  id: number;
  name: string;
  email: string;
}

type UserKey = keyof User;      // 'id' | 'name' | 'email'
type UserIdType = User['id'];   // number

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

const user: User = { id: 1, name: 'Alice', email: 'a@example.com' };
const id = getProperty(user, 'id');   // id: number

Tooling & Configuration

Configure the TypeScript compiler and declaration files

Intermediate
2 examples

Basic tsconfig.json TS

Enable strict mode and modern JavaScript targets

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "moduleResolution": "node",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "dist"
  },
  "include": ["src"]
}

Ambient Declarations & .d.ts TS

Describe globals and modules without implementations

// globals.d.ts - declare global types
declare global {
  interface Window {
    appVersion: string;
  }
}

export {};

// types.d.ts - declare module
declare module 'my-lib' {
  export function doSomething(value: string): void;
}

Best Practices & Tips

Patterns for safe, maintainable TypeScript code

Expert
2 examples

unknown vs any & Exhaustive Checks TS

Prefer unknown over any and ensure exhaustive unions

function handleValue(value: unknown): void {
  if (typeof value === 'string') {
    console.log(value.toUpperCase());
  } else if (typeof value === 'number') {
    console.log(value.toFixed(2));
  } else {
    console.log('Unknown value', value);
  }
}

type Shape =
  | { kind: 'circle'; radius: number }
  | { kind: 'square'; size: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius * shape.radius;
    case 'square':
      return shape.size * shape.size;
    default: {
      // compile-time error if a new case is not handled
      const _exhaustive: never = shape;
      return _exhaustive;
    }
  }
}

Organizing Types & Using import type TS

Keep types in dedicated modules and avoid runtime imports

// user.types.ts
export interface User {
  id: number;
  name: string;
  email: string;
}

export type UserId = User['id'];

// user.service.ts
import type { User, UserId } from './user.types';

export function getUser(id: UserId): Promise<User> {
  return fetch('/api/users/' + id).then(res => res.json());
}

TypeScript Best Practices & Tips

Type Safety

  • Enable strict mode in tsconfig.json
  • Prefer unknown over any for unsafe inputs
  • Use exhaustive checks with never in unions

Project Setup

  • Keep src/ strictly typed; block JS in strict TS projects
  • Use path aliases for large codebases
  • Generate d.ts files for libraries you publish

Best Practices

  • Keep types and interfaces close to usage or in dedicated files
  • Prefer composition of types over deep inheritance
  • Document complex types and utility helpers for your team
Buy Me A Coffee