124 — Object Oriented TypeScript #1, Introduction (Procedures vs Classes, Inheritance, First Object)
Full companion narrative for j4orz's Object Oriented TypeScript #1 — the Alice-and-Bob Mario-clone story that motivates classes, inheritance, overriding, and the first live Player instantiation
Video: 124 · Objected Oriented Typescript #1 - Introduction · watch · ~10:17 · j4orz
Source: this is a written companion to the video. Part of the Favorite TypeScript Videos curated path, module 05 (objects and interfaces) — the theory pass for the whole OOP arc: the Alice-and-Bob change-cost story motivates the class drill pass in 126, the hardcoded character numbers are the same starting smell as the hardcoded greeting in 125, and the
Playerfactoring here is the behavioral twin of theIAdminUser extends IUserfactoring in 128.
White lecture slides plus a closing live-coding beat in oop.ts. Frame one is a bare “Object Oriented Programming” title card; the middle frames walk a single running story — two programmers, Alice (procedures) and Bob (classes), given the same Mario-clone spec of Mario, Luigi, and Toad moving on arrow keys with per-character voice sounds. The final frame lands in the editor: a Player class with health and speed fields and a greet() method, instantiated as mario. Prerequisite call up front: variables, control flow, and functions first, via his TypeScript Fundamentals series if needed. His change mantra: “in software development, change is constant. You’re always going to get it.”

procedures dispatch on data, classes bind behavior to the thing itself
Alice’s move(characterNum) and playSound(characterNum) take a character number (0 for Mario, 1 for Luigi, 2 for Toad) and, for sound, look up which MP3 to play. Bob writes one class per character, each with its own move() and playSound() hardwired to its own voice file — “there’s no need for checking because we know — for the Mario class, playSound is always going to play Mario’s MP3.”
// Alice: procedures dispatch on a number
function move(characterNum: number): void {
/* make the character move */
}
function playSound(characterNum: number): void {
/* use characterNum to look up which .mp3 to play, and play it */
}
// Bob: each class carries its own behavior
class Mario {
move(): void {
/* code to move Mario */
}
playSound(): void {
/* play .mp3 file with Mario's voice */
}
}

new requirements punish tested procedural code, classes absorb them by addition
The spec changes: Peach arrives, and her voice file must be WAV, not MP3. Alice edits her already-tested playSound into an if/else on the character number — the slide’s margin note reads “Changing already tested code is not the best.” Bob adds a Peach class and touches nothing existing. Then Peach breaks the movement assumption too — she flies — so Alice branches move as well while Bob only edits Peach’s move.
// Alice after the Peach change: tested code re-opened
function playSound(characterNum: number): void {
// if character is not peach: existing mp3 lookup; else: play peach .wav file
}
function move(characterNum: number): void {
// if character is peach: fly; otherwise move normally
}
// Bob after the Peach change: one new class, nothing else touched
class Peach {
move(): void {
/* peach flies */
}
playSound(): void {
/* play peach .wav file */
}
}

four parallel classes duplicate code, then inheritance factors it into one superclass
With Peach added, Mario, Luigi, Toad, and Peach each implement their own move() and playSound(): “a lot of code duplication, which is bad for maintainability reasons.” The video refuses to hide this — “this is not the final design though. It’s very important to remember.” Duplication is presented as the real cost of naive OOP, motivating the next move: Bob pulls the shared methods into a Player class and links the four character classes to it via inheritance — Player is the superclass, the four are subclasses, and “there are no more methods in these classes, so we only have to maintain one class.” The slide’s own terminology wobbles between “player” and “character” for the superclass name; they are the same box.
// one maintained class, four empty inheritors
class Player {
move(): void {
/* make the character move */
}
playSound(): void {
/* play this character's voice file */
}
}
class Mario extends Player {}
class Luigi extends Player {}
class Toad extends Player {}
class Peach extends Player {}

overriding re-specializes inherited behavior per subclass
Inheritance raises the Peach question again: she inherits both methods, so how does she fly and play WAV? Answer: “we can redeclare the move function and the playSound function on the Peach class” — “and what that does is override the Player class’s functions.” Rule stated generally: “you have to override a subclass whenever you want to change or extend existing behavior from the super class.”
// Peach keeps the inheritance link but replaces two behaviors
class Peach extends Player {
move(): void {
/* peach specific: fly */
}
playSound(): void {
/* peach specific: play .wav */
}
}

class versus object: blueprint versus instance, knows versus does
“A class is not an object. But it’s used to construct an object. A class is a blueprint for an object” — each object gets its own values for the class’s instance variables, for example many players with different healths, velocities, and positions. The Player box splits along knows and does: instance variables are what the player knows (health, velocity, position); methods — functions on classes — are what it does (move, crouch, attack). This is the same knows-versus-does line 125 draws for components: props are what a component knows, declared up front.
// knows (instance variables) vs does (methods)
class Player {
health: number; // knows
velocity: number; // knows
position: number; // knows
move(): void {} // does
crouch(): void {} // does
attack(): void {} // does
}

first live class: Player built, instantiated with new, driven via dots
In oop.ts: class Player with health: number; speed: number (autocomplete confirms the annotation) and a greet() method logging 'Hello world'. Then const mario = new Player() — “new creates an instance of the blueprint; instance and object are the same thing” — with mario.health = 10 and mario.speed = 1 set through the dot operator and mario.greet() called. Compiled and run: “we get hello world.” Honest caveat included: direct field writes break encapsulation, “and I’ll get into that in a later video.”
// first live class and object
class Player {
health: number;
speed: number;
greet(): void {
console.log("Hello world");
}
}
const mario = new Player(); // new: instantiate the blueprint
mario.health = 10; // dot access to instance variables
mario.speed = 1;
mario.greet(); // "Hello world"

The theory pass for the OOP arc: 126 re-teaches this video’s blueprint, instance, knows-versus-does, and first-instantiation beats hands-on — student with name and rollNo fields, new student(), three instances — the drill pass for this video’s theory pass. The factoring move here (shared behavior pulled up into Player) is the behavioral twin of the shape factoring in 128, where IAdminUser extends IUser pulls shared shape upward. Extracting the superclass is also constructive interface and blueprint design in 127’s terms — the pillar-4 move applied to OOP.
Prev: 123 — Factory Patterns · Next: 125 — React TypeScript Tutorial 3, Typing Props
Transcript notes: Class, method, and variable names (Player, Mario, Luigi, Toad, Peach, move, playSound, greet, health, speed, mario, oop.ts) verified against the slide and editor frames. frame-0001.png (title card) through frame-0007.png (live instantiation). timestamps ~00:00 (premise) through ~09:00 (first run) across the ~10:17 runtime.