TypeScript and JavaScript OOP Track
Learning Objectives
Understand prototypes, constructor functions and class syntax.
Use extends/super, #private vs private, interfaces and structural typing, abstract classes and generics.
Handle this binding, mixins, composition and runtime type erasure.
Prerequisites: Stages 1–2.
Intuition and Definition
JavaScript objects inherit from other objects through the prototype chain. class syntax (ES2015) is a cleaner way to set up constructor functions and prototypes. TypeScript adds a structural static type system — interfaces, generics, access modifiers — that is erased at run time.
class, extends, super, static implements, abstract, parameter types #field (run-time enforced) private/protected (compile time) interface, structural compatibility depends on call site; arrow functions capture this parameter types generic functions/classes, constraints, conditional/mapped types functions returning classes typed mixin patterns
Why It Matters
TypeScript is the default language for large front-end and Node.js codebases. Its OO features look like Java/C# but behave differently at run time — a frequent source of bugs.
Syntax and Language Notes
abstract class Shape {
abstract area(): number;
describe() { return `${this.constructor.name} with area ${this.area().toFixed(2)}`; }
}Complete Example
// 1. prototypes underneath classes
function LegacySensor(this: any, id: string) { this.id = id; }
LegacySensor.prototype.describe = function () { return `legacy ${this.id}`; };
const legacy = new (LegacySensor as any)("oil");
console.log(legacy.describe(), Object.getPrototypeOf(legacy) === LegacySensor.prototype);
// 2. classes, #private, abstract, structural interfaces
interface Readable { read(): number; }
abstract class Sensor implements Readable {
#reads = 0; // real run-time privacy
readonly id: string;
constructor(id: string) { this.id = id; }
read(): number { this.#reads++; return this.sample(); }
protected abstract sample(): number;
get reads() { return this.#reads; }
}
class Coolant extends Sensor {
protected sample() { return 90.5; }
}
const c = new Coolant("coolant");
c.read(); c.read();
console.log(c.id, c.read(), "reads:", c.reads, "proto chain:", Object.getPrototypeOf(Coolant.prototype) === Sensor.prototype);
const plain: Readable = { read: () => 42 }; // structurally compatible object literal
console.log("structural:", plain.read());
// 3. mixin: a function that adds behaviour to any class
type Ctor<T
…legacy oil true
coolant 90.5 reads: 3 proto chain: true
structural: 42
c2 2026-10-13T08:00Z true
registry has coolant: trueStep-by-Step Walkthrough
The legacy constructor function shows what class compiles to: an object whose prototype holds shared methods.
#reads is private at run time; protected abstract sample() is enforced only by the compiler.
Any object with a read() method satisfies Readable — no implements needed.
The mixin returns a new class extending its argument; instances are still instanceof Coolant.
add is an arrow-function field, so calling it detached keeps this bound to the registry.
Common Mistakes
Expecting private to hide data at run time — use #private.
Losing this when passing methods as callbacks.
instanceof with interfaces — impossible; interfaces don't exist at run time.
Trusting types for external data — validate JSON at run time (zod, valibot, ajv).
Deep class hierarchies in front-end code — composition, hooks and functions are idiomatic.
Parameter properties and enums with type-stripping runtimes. constructor(readonly id: string) and enum generate JavaScript, so Node's built-in type stripping (and other "erasable syntax only" tools) reject them; declare the field explicitly and use union types or as const objects — or compile with tsc.
Trade-offs and Alternatives
Much modern TypeScript is functional (React function components, plain objects + types). Classes remain useful for stateful services, SDK clients, error types and domain models.
Real-World Use
Angular (class-based components, DI), NestJS services, TypeORM entities, SDK clients (AWS, Stripe), VS Code extensions, Node.js back ends.
Practice
Q1. (Easy) Which privacy mechanism is enforced at run time?
Answer: #private fields.
Q2. (Medium) Why can an object literal be passed where an interface is expected?
Answer: TypeScript is structurally typed: compatibility depends on shape, not declared names.
Q3. (Advanced) What does class syntax do with methods, in prototype terms?
Answer: Methods are defined once on ClassName.prototype; instances get them through the prototype chain. Fields (including arrow-function fields) are created per instance in the constructor.
Interview Question
Q1. "How is JavaScript inheritance different from Java's?"
Answer: JavaScript uses prototype delegation: objects link to other objects, and property lookup walks the chain at run time; class is syntax over this. Methods can be added or changed on prototypes at run time, any object can be a prototype, and there's no compile-time type system in JS itself. Java has class-based inheritance fixed at compile time with nominal types and run-time type information. TypeScript adds compile-time structural types to JS but erases them, so run-time behaviour is still prototype-based.
Quick Quiz
Q1. What are TypeScript interfaces at run time?
Answer: Nothing — they are erased.
Q2. What is a mixin in TS/JS?
Answer: A function that takes a class and returns a subclass with added members.
Q3. How do you keep this bound for a method used as a callback?
Answer: Arrow-function field, .bind(this), or a wrapper lambda.
Summary
JS: prototypes underneath class; this depends on the call. TS: structural, erased types, compile-time access modifiers, #private at run time.
Related: Modules 8, 11, 17, 82.