L07Classes L08Generics L09Enums L10Type Narrowing L11Modules
INTER
Module 2 — Intermediate Track

Level
Up.

Five intermediate lessons covering Classes, Generics, Enums, Type Narrowing, and Modules — the concepts that separate beginners from TypeScript developers.

🏛️ Classes
🧬 Generics
🔢 Enums
🛡️ Type Narrowing
🗂️ Modules
07Classes 08Generics 09Enums 10Type Narrowing 11Modules
LESSON 07
Classes NEW

Blueprints for creating objects — TypeScript supercharges OOP with access modifiers and strict typing.

Classes are blueprints for creating objects. TypeScript adds access modifiers and strong typing on top of JavaScript classes, making Object-Oriented Programming safer and more expressive.

person.ts
class Person {
  name: string;
  age: number;

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

  greet(): string {
    return `Hi, I'm ${this.name} and I'm ${this.age}.`;
  }
}

const alice = new Person("Alice", 25);
console.log(alice.greet()); // Hi, I'm Alice and I'm 25.
Access Modifiers

public

Accessible from anywhere (default)

private

Only accessible inside the class

protected

Class and its subclasses only

bank-account.ts
class BankAccount {
  public owner: string;
  private balance: number;

  constructor(owner: string, initialBalance: number) {
    this.owner   = owner;
    this.balance = initialBalance;
  }

  deposit(amount: number): void {
    this.balance += amount;
  }

  getBalance(): number {
    return this.balance; // ✅ private access inside class
  }
}

const acc = new BankAccount("Alice", 1000);
acc.deposit(500);
console.log(acc.getBalance()); // 1500
// acc.balance ❌ Error — private!
Shorthand + Inheritance

Verbose way

class Dog {
  name: string;
  breed: string;
  constructor(
    name: string,
    breed: string
  ) {
    this.name  = name;
    this.breed = breed;
  }
}

✨ Shorthand way

class Dog {
  constructor(
    public name: string,
    public breed: string
  ) {}
}

// Same result — much cleaner!
inheritance.ts
class Animal {
  constructor(public name: string) {}
  speak(): string { return `${this.name} makes a sound.`; }
}

class Dog extends Animal {
  constructor(name: string, public breed: string) {
    super(name); // call parent constructor
  }
  speak(): string { return `${this.name} barks!`; }
}

const dog = new Dog("Rex", "Labrador");
console.log(dog.speak()); // Rex barks!
Abstract classes
abstract.ts
abstract class Shape {
  abstract getArea(): number; // must be implemented by subclass

  describe(): string {
    return `Area: ${this.getArea()}`;
  }
}

class Circle extends Shape {
  constructor(private radius: number) { super(); }
  getArea(): number { return Math.PI * this.radius ** 2; }
}

const c = new Circle(5);
console.log(c.describe()); // Area: 78.53...
LESSON 08
Generics NEW

Write once, work with any type — the secret to truly reusable TypeScript code.

Generics let you write reusable, type-safe code that works with different data types. Instead of writing the same function multiple times for each type, you write it once using <T>.

❌ Without generics

function getFirstNum(
  arr: number[]
): number {
  return arr[0];
}
function getFirstStr(
  arr: string[]
): string {
  return arr[0];
}
// Duplicate code!

✅ With generics

function getFirst<T>(
  arr: T[]
): T {
  return arr[0];
}

getFirst([1, 2, 3]);    // number
getFirst(["a","b"]);  // string
getFirst([true]);     // boolean
Generic interfaces
api-response.ts
interface ApiResponse<T> {
  data: T;
  success: boolean;
  message: string;
}

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

// Response containing a User
const userRes: ApiResponse<User> = {
  data: { name: "Alice", email: "[email protected]" },
  success: true,
  message: "User found"
};

// Response containing a number
const countRes: ApiResponse<number> = {
  data: 42,
  success: true,
  message: "Count retrieved"
};
Constraints with extends
constraints.ts
// T must have a .length property
function logLength<T extends { length: number }>(value: T): void {
  console.log(value.length);
}

logLength("hello");       // 5 ✅ strings have .length
logLength([1, 2, 3]);     // 3 ✅ arrays have .length
// logLength(42);         ❌ numbers don't have .length

// Multiple type parameters
function merge<T, U>(obj1: T, obj2: U): T & U {
  return { ...obj1, ...obj2 } as T & U;
}

const merged = merge({ name: "Alice" }, { age: 25 });
// merged: { name: string, age: number }
LESSON 09
Enums NEW

Named constants that make your code readable — goodbye magic strings and numbers.

Enums (enumerations) let you define a set of named constants. They replace "magic" values with meaningful names, making code self-documenting.

Numeric Enums

Default: auto-numbered from 0. Or set a custom start value.

String Enums

Explicit string values. More debuggable and preferred in modern TS.

Const Enums

Inlined at compile time. Zero runtime cost — best performance.

enums.ts
// Numeric enum — auto-numbered
enum Direction {
  Up,    // 0
  Down,  // 1
  Left,  // 2
  Right  // 3
}

let move: Direction = Direction.Up;
console.log(move); // 0

// String enum — preferred!
enum Status {
  Pending  = "PENDING",
  Active   = "ACTIVE",
  Inactive = "INACTIVE"
}

enum UserRole {
  Admin = "admin",
  User  = "user",
  Guest = "guest"
}

function checkRole(role: UserRole): void {
  if (role === UserRole.Admin) {
    console.log("Welcome, admin!");
  }
}

// const enum — compiled away for performance
const enum Color {
  Red = "RED", Green = "GREEN", Blue = "BLUE"
}

let fav: Color = Color.Blue;
// Compiles to: let fav = "BLUE" — no enum object!
LESSON 10
Type Narrowing NEW

Teach TypeScript to figure out the exact type from a broad one — essential for union types.

Type narrowing is how TypeScript figures out a more specific type from a broader one. This is essential when working with union types or values that could be different things at runtime.

typeof-guard.ts
// typeof guard — for primitives
function formatValue(value: string | number): string {
  if (typeof value === "string") {
    // TypeScript knows: value is string here
    return value.toUpperCase();
  } else {
    // TypeScript knows: value is number here
    return value.toFixed(2);
  }
}

console.log(formatValue("hello")); // "HELLO"
console.log(formatValue(3.14159)); // "3.14"
instanceof-guard.ts
// instanceof guard — for classes
class Cat { meow() { return "Meow!"; } }
class Dog { bark() { return "Woof!"; } }

function makeSound(animal: Cat | Dog): string {
  if (animal instanceof Cat) {
    return animal.meow(); // TS knows: Cat
  }
  return animal.bark();   // TS knows: Dog
}

makeSound(new Cat()); // "Meow!"
makeSound(new Dog()); // "Woof!"
in-operator.ts
// "in" guard — check if property exists
interface Admin       { name: string; adminLevel: number; }
interface RegularUser { name: string; email: string; }

function describeUser(user: Admin | RegularUser): string {
  if ("adminLevel" in user) {
    return `Admin level: ${user.adminLevel}`; // Admin
  }
  return `Email: ${user.email}`; // RegularUser
}

// Type assertion with "as" — use carefully!
const input = document.getElementById("name") as HTMLInputElement;
console.log(input.value); // TS now knows .value exists
LESSON 11
Modules NEW

Split your code across files — the modern way to organize growing TypeScript projects.

As your projects grow, you'll split code into multiple files (modules). TypeScript fully supports ES Modules — the modern standard for sharing code between files.

📤 Exporting

// math.ts
export function add(
  a: number, b: number
): number {
  return a + b;
}

export const PI: number = 3.14159;

// default export (one per file)
export default function multiply(
  a: number, b: number
): number {
  return a * b;
}

📥 Importing

// app.ts
import { add, PI } from "./math";
import multiply from "./math";
import * as Math from "./math";

console.log(add(2, 3));  // 5
console.log(PI);         // 3.14159
console.log(multiply(3,4)); // 12
console.log(Math.add(1,2)); // 3
Exporting types
types.ts → userService.ts
// types.ts — share your types!
export interface User {
  id: number;
  name: string;
  email: string;
}

export type Status = "active" | "inactive" | "banned";

// userService.ts — import with "import type" (preferred)
import type { User, Status } from "./types";

function getUser(id: number): User {
  return { id, name: "Alice", email: "[email protected]" };
}
Project structure
project-structure
my-project/
├── src/
│   ├── types/
│   │   └── index.ts        ← all shared types & interfaces
│   ├── utils/
│   │   └── helpers.ts      ← utility functions
│   ├── services/
│   │   └── userService.ts  ← business logic
│   └── index.ts            ← main entry point
├── tsconfig.json            ← TypeScript config
└── package.json

💡 Pro tip: Run tsc --init to auto-generate a tsconfig.json for your project with all options and their documentation commented in.

Module 2 Complete!

You're now writing intermediate TypeScript. Time to go advanced.