Mastering TypeScript from Scratch – Part 2: Intermediate Concepts

Level up your TypeScript skills by learning interfaces, classes, generics, utility types, and more — the building blocks for real-world projects.

Hey everyone 👋, I’m back again with Part 2 of my TypeScript learning series! If you missed the first part…


This content originally appeared on DEV Community and was authored by MUHAMMAD USMAN AWAN

Level up your TypeScript skills by learning interfaces, classes, generics, utility types, and more — the building blocks for real-world projects.

Hey everyone 👋, I’m back again with Part 2 of my TypeScript learning series! If you missed the first part, we covered all the beginner foundations of TypeScript — from setup and basic types to unions and enums. Now, it’s time to take a step forward into the intermediate concepts that make TypeScript powerful in real-world applications. In this part, we’ll explore interfaces vs types, classes and OOP, generics, type narrowing, modules, utility types, DOM typings, and error handling. Buckle up — this is where TS really starts to shine for actual projects! 🚀

📗 Intermediate TypeScript (Core Power)

🔹 1. Interfaces vs Type Aliases Interface

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

const user1: User = { id: 1, name: "Usman" };

Type Alias

type UserType = {
  id: number;
  name: string;
};

const user2: UserType = { id: 2, name: "Ali" };

Key difference:

  • Interfaces: can be extended & merged.
  • Types: more flexible (unions, intersections), but can’t merge.

Extending Interfaces

interface Person {
  name: string;
}
interface Employee extends Person {
  salary: number;
}

const emp: Employee = { name: "Ali", salary: 50000 };

Declaration Merging (only interfaces!)

interface Car {
  brand: string;
}
interface Car {
  year: number;
}

const myCar: Car = { brand: "Toyota", year: 2023 };

🔹 2. Classes & OOP in TS

class Animal {
  public name: string;       // accessible everywhere
  protected age: number;     // accessible in class + subclass
  private secret: string;    // only inside this class
  static count: number = 0;  // belongs to class, not instance
  readonly type: string;     // cannot be reassigned

  constructor(name: string, age: number, type: string) {
    this.name = name;
    this.age = age;
    this.secret = "hidden";
    this.type = type;
    Animal.count++;
  }

  public speak(): void {
    console.log(`${this.name} makes a sound`);
  }
}

class Dog extends Animal {
  constructor(name: string, age: number) {
    super(name, age, "dog");
  }

  public bark() {
    console.log(`${this.name} barks!`);
  }
}

const d = new Dog("Buddy", 3);
d.speak(); // Buddy makes a sound
d.bark();  // Buddy barks!

Abstract Classes

abstract class Shape {
  abstract area(): number; // must be implemented in subclass
}

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

const c = new Circle(5);
console.log(c.area());

🔹 3. Generics

Generic Function

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

console.log(identity<string>("Hello")); // Hello
console.log(identity<number>(123));     // 123

Generic Interfaces

interface Box<T> {
  value: T;
}

let stringBox: Box<string> = { value: "text" };
let numberBox: Box<number> = { value: 100 };

Generic Constraints

function logLength<T extends { length: number }>(item: T) {
  console.log(item.length);
}

logLength("hello");  // ✅
logLength([1,2,3]);  // ✅

Default Generic Types

interface ApiResponse<T = string> {
  data: T;
}

const res1: ApiResponse = { data: "ok" };   // string by default
const res2: ApiResponse<number> = { data: 42 };

🔹 4. Type Narrowing

function printId(id: string | number) {
  if (typeof id === "string") {
    console.log("String ID:", id.toUpperCase());
  } else {
    console.log("Number ID:", id.toFixed(2));
  }
}

Instanceof

class Cat { meow() {} }
class Dog { bark() {} }

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

Discriminated Union

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number };

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

🔹 5. Modules & Namespaces

ES Modules

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

// file: app.ts
import multiply, { add } from "./math";

console.log(add(2, 3));
console.log(multiply(2, 3));

Namespaces (rarely used now, mostly in legacy code)

namespace Utils {
  export function greet(name: string) {
    return `Hello, ${name}`;
  }
}

console.log(Utils.greet("Usman"));

🔹 6. Type Assertions & Casting

let value: unknown = "hello";
let strLength: number = (value as string).length;

let el = document.querySelector("#myInput") as HTMLInputElement;
el.value = "typed!";

Non-null Assertion (!)

let btn = document.querySelector("#btn")!;
btn.addEventListener("click", () => console.log("Clicked!"));

🔹 7. Utility Types (Built-in)

interface User {
  id: number;
  name: string;
  age?: number;
}

// Make all properties optional
type PartialUser = Partial<User>;

// Make all properties required
type RequiredUser = Required<User>;

// Make all properties readonly
type ReadonlyUser = Readonly<User>;

// Pick selected properties
type UserName = Pick<User, "name">;

// Omit selected properties
type UserWithoutAge = Omit<User, "age">;

// Map keys to type
type Roles = Record<"admin" | "user", boolean>;

// Extract function return/params
function getUser() { return { id: 1, name: "Ali" }; }
type UserReturn = ReturnType<typeof getUser>;

🔹 8. Working with DOM & Browser APIs

const input = document.querySelector<HTMLInputElement>("#username");
if (input) {
  input.value = "Usman Awan";
}

document.addEventListener("click", (e: MouseEvent) => {
  console.log(e.clientX, e.clientY);
});

document.addEventListener("keydown", (e: KeyboardEvent) => {
  console.log(e.key);
});

🔹 9. Error Handling Types

Typing try/catch

try {
  throw new Error("Something went wrong");
} catch (err: unknown) {
  if (err instanceof Error) {
    console.error("Error:", err.message);
  }
}

unknown vs any

  • any: bypasses type checking (unsafe).
  • unknown: forces you to narrow type before using.
let val: unknown = "test";
val.toUpperCase(); // ❌ error
if (typeof val === "string") {
  console.log(val.toUpperCase()); // ✅ safe
}

✅ Intermediate Summary

You now know how to:

  • Use interfaces vs types, extend & merge.
  • Write classes, abstract classes, OOP with access modifiers.
  • Work with generics for reusable code.
  • Narrow types with typeof, instanceof, discriminated unions.
  • Import/export using modules.
  • Safely cast values & use type assertions.
  • Apply utility types like Partial, Pick, Omit.
  • Handle DOM events with strict types.
  • Properly type error handling.

That’s a wrap for Part 2 (Intermediate) of our TypeScript journey! 🎉 You now know how to confidently use interfaces, classes, generics, utility types, and type-safe DOM handling. With these tools, you’re ready to start applying TypeScript in real projects — whether it’s a Node.js API, a React app, or even a library. In the next and final part (Advanced), we’ll dive into conditional types, mapped types, advanced generics, declaration files, and the latest TS features. Stay tuned, it’s going to be next-level! 💡

Part 1: Beginner Foundations

Thanks for reading! 🙌
Until next time, 🫡
Usman Awan (your friendly dev 🚀)


This content originally appeared on DEV Community and was authored by MUHAMMAD USMAN AWAN


Print Share Comment Cite Upload Translate Updates
APA

MUHAMMAD USMAN AWAN | Sciencx (2025-08-25T08:59:54+00:00) Mastering TypeScript from Scratch – Part 2: Intermediate Concepts. Retrieved from https://www.scien.cx/2025/08/25/mastering-typescript-from-scratch-part-2-intermediate-concepts-3/

MLA
" » Mastering TypeScript from Scratch – Part 2: Intermediate Concepts." MUHAMMAD USMAN AWAN | Sciencx - Monday August 25, 2025, https://www.scien.cx/2025/08/25/mastering-typescript-from-scratch-part-2-intermediate-concepts-3/
HARVARD
MUHAMMAD USMAN AWAN | Sciencx Monday August 25, 2025 » Mastering TypeScript from Scratch – Part 2: Intermediate Concepts., viewed ,<https://www.scien.cx/2025/08/25/mastering-typescript-from-scratch-part-2-intermediate-concepts-3/>
VANCOUVER
MUHAMMAD USMAN AWAN | Sciencx - » Mastering TypeScript from Scratch – Part 2: Intermediate Concepts. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/08/25/mastering-typescript-from-scratch-part-2-intermediate-concepts-3/
CHICAGO
" » Mastering TypeScript from Scratch – Part 2: Intermediate Concepts." MUHAMMAD USMAN AWAN | Sciencx - Accessed . https://www.scien.cx/2025/08/25/mastering-typescript-from-scratch-part-2-intermediate-concepts-3/
IEEE
" » Mastering TypeScript from Scratch – Part 2: Intermediate Concepts." MUHAMMAD USMAN AWAN | Sciencx [Online]. Available: https://www.scien.cx/2025/08/25/mastering-typescript-from-scratch-part-2-intermediate-concepts-3/. [Accessed: ]
rf:citation
» Mastering TypeScript from Scratch – Part 2: Intermediate Concepts | MUHAMMAD USMAN AWAN | Sciencx | https://www.scien.cx/2025/08/25/mastering-typescript-from-scratch-part-2-intermediate-concepts-3/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.