diff --git a/courses/backend/README.md b/courses/backend/README.md
index 0825b4ba..796e91cf 100644
--- a/courses/backend/README.md
+++ b/courses/backend/README.md
@@ -77,6 +77,7 @@ Total: 18 weeks
- [ ] Use logging and debugging tools to monitor and troubleshoot applications
- [ ] Connect to databases and implement CRUD operations
- [ ] Test APIs using Postman
+- [ ] Document APIs using Swagger/OpenAPI
### [Specialist Career Training](/shared-modules/specialist-career-training)
diff --git a/courses/frontend/advanced-javascript/week4/README.md b/courses/frontend/advanced-javascript/week4/README.md
index 75d2dd93..23fe4226 100644
--- a/courses/frontend/advanced-javascript/week4/README.md
+++ b/courses/frontend/advanced-javascript/week4/README.md
@@ -18,8 +18,16 @@ By the end of this session, you will be able to:
- [ ] Instantiate objects from classes using `new`
- [ ] Use Methods and constructors
- [ ] Use Static methods
- - [ ] Use inheritance with `extends` and `super()`
- [ ] Understand the difference between classes vs objects
+- [ ] Use **inheritance** and **composition** to share behavior between classes
+ - [ ] Use inheritance with `extends` and `super()`
+ - [ ] Recognize when inheritance is a good fit ("is-a" relationship)
+ - [ ] Use composition ("has-a") as an alternative to inheritance
+- [ ] _(optional)_ Recognise common **design patterns** and when to apply them
+ - [ ] Strategy — swap behavior by passing in a different object
+ - [ ] Factory — hide object creation complexity behind a function
+ - [ ] Observer — notify listeners when state changes
+ - [ ] Singleton — ensure only one instance of a class exists
```js
class Comment {
diff --git a/courses/frontend/advanced-javascript/week4/assignment.md b/courses/frontend/advanced-javascript/week4/assignment.md
index 1374fbd2..415b7ecc 100644
--- a/courses/frontend/advanced-javascript/week4/assignment.md
+++ b/courses/frontend/advanced-javascript/week4/assignment.md
@@ -4,6 +4,8 @@ For this week's assignment we will create a web application that generates a scr
We use [Rapid API](https://rapidapi.com/apishub/api/website-screenshot6/?utm_source=RapidAPI.com%2Fguides&utm_medium=DevRel&utm_campaign=DevRel) to generate a screenshot and the [crudcrud API](https://crudcrud.com/) to save the screenshot.
+
+
## Technical specifications
1. User can enter a URL for a website and it will send back a screenshot of the website using the website-screenshot API
@@ -21,6 +23,244 @@ Look at your interface and think about what parts can be modeled as classes —
For the error system, think about what kinds of errors can happen in your app — what if the user submits an empty URL? What if the API returns a bad response? What if the network is down? You might end up with classes like `ValidationError`, `ApiError`, or something else entirely — it's up to you.
+---
+
+## API Guides
+
+### The Screenshot API (Rapid API)
+
+Sign up at [RapidAPI](https://rapidapi.com) and subscribe to the **website-screenshot6** API (free tier is enough). You will get an API key.
+
+The API takes a website URL and returns **JSON** with a `screenshotUrl` field — a direct link to the generated image you can use in an `` tag.
+
+```js
+async function fetchScreenshot(websiteUrl) {
+ const response = await fetch(
+ `https://website-screenshot6.p.rapidapi.com/screenshot?url=${encodeURIComponent(websiteUrl)}&width=1920&height=1080`,
+ {
+ method: "GET",
+ headers: {
+ "x-rapidapi-host": "website-screenshot6.p.rapidapi.com",
+ "x-rapidapi-key": "YOUR_API_KEY_HERE",
+ },
+ },
+ );
+
+ if (!response.ok) {
+ throw new Error(`Screenshot API error: ${response.status}`);
+ }
+
+ // The response is JSON: { screenshotUrl: "https://..." }
+ const data = await response.json();
+ return data.screenshotUrl;
+}
+```
+
+> **Keep your API key out of git.** Put it in a `secret.js` file and add that file to `.gitignore`.
+
+---
+
+### The crudcrud API
+
+[crudcrud.com](https://crudcrud.com/) gives you a free, temporary REST API endpoint for storing JSON data. Go to the site and you will get a unique ID — your endpoint will look like:
+
+```text
+https://crudcrud.com/api/YOUR_UNIQUE_ID
+```
+
+You can create any resource name you like after it, for example `/screenshots`. For this app you need three operations:
+
+| What you want to do | Method | URL |
+| ------------------------- | -------- | --------------------- |
+| Get all saved screenshots | `GET` | `.../screenshots` |
+| Save a new screenshot | `POST` | `.../screenshots` |
+| Delete one screenshot | `DELETE` | `.../screenshots/:id` |
+
+crudcrud automatically assigns an `_id` field to each item you POST. You will need that `_id` to delete items later.
+
+#### Save a screenshot
+
+```js
+async function saveScreenshot(websiteUrl, screenshotUrl) {
+ const response = await fetch(
+ "https://crudcrud.com/api/YOUR_UNIQUE_ID/screenshots",
+ {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ websiteUrl, screenshotUrl }),
+ },
+ );
+
+ if (!response.ok) {
+ throw new Error(`Failed to save: ${response.status}`);
+ }
+
+ // crudcrud returns the saved object with its _id
+ const saved = await response.json();
+ return saved; // { _id: "abc123", websiteUrl: "https://example.com", screenshotUrl: "https://..." }
+}
+```
+
+#### Load all screenshots
+
+```js
+async function loadScreenshots() {
+ const response = await fetch(
+ "https://crudcrud.com/api/YOUR_UNIQUE_ID/screenshots",
+ );
+
+ if (!response.ok) {
+ throw new Error(`Failed to load: ${response.status}`);
+ }
+
+ const items = await response.json();
+ return items; // Array of { _id, websiteUrl, screenshotUrl }
+}
+```
+
+#### Delete a screenshot
+
+```js
+async function deleteScreenshot(id) {
+ const response = await fetch(
+ `https://crudcrud.com/api/YOUR_UNIQUE_ID/screenshots/${id}`,
+ { method: "DELETE" },
+ );
+
+ if (!response.ok) {
+ throw new Error(`Failed to delete: ${response.status}`);
+ }
+}
+```
+
+> **Note:** crudcrud endpoints expire after a few days on the free plan. If your app suddenly stops working, go to crudcrud.com and get a new unique ID. Keep your unique ID in `secret.js` alongside your API key.
+
+---
+
+## Using `render()` — when and how
+
+The `render()` method is how a class puts itself on the page. The idea: **the class owns its own DOM element**. Call `render()` to create or update that element, then append the returned element somewhere in the DOM.
+
+Use this base class as a starting point — every UI class in your app should extend it:
+
+```js
+class UIComponent {
+ constructor() {
+ this.element = null;
+ }
+
+ render() {
+ throw new Error("render() must be implemented by subclass");
+ }
+}
+```
+
+A `Screenshot` class is a natural fit here — it holds the website URL, the screenshot image URL, and its crudcrud `_id`, and it knows how to display itself. Think about:
+
+- What data does it need? (constructor)
+- What does its card look like? (render)
+- What can it do? (methods like delete)
+
+```js
+class Screenshot extends UIComponent {
+ constructor(websiteUrl, screenshotUrl, id) {
+ super();
+ this.websiteUrl = websiteUrl;
+ this.screenshotUrl = screenshotUrl; // direct image URL from the API
+ this.id = id; // _id from crudcrud — needed to delete later
+ }
+
+ render() {
+ // create this.element if it doesn't exist yet, then build the HTML
+ // use this.screenshotUrl directly as the src
+ // return this.element so the caller can append it to the page
+ }
+
+ async delete() {
+ // call the crudcrud delete function, then remove this.element from the DOM
+ }
+}
+
+// Usage
+const card = new Screenshot(
+ "https://example.com",
+ "https://storage.linebot.site/...",
+ "abc123",
+);
+document.getElementById("screenshots-list").appendChild(card.render());
+```
+
+**When to call `render()`:**
+
+- Right after creating a new instance — to show it on screen
+- After data on the instance changes and the DOM should reflect it
+
+---
+
+## Error handling — when and how
+
+Not all errors are the same. A user typing nothing in the input is different from the API being down. Custom error classes let you handle each case differently.
+
+Here is a starting point — adapt it to fit your actual app:
+
+```js
+class ValidationError extends Error {
+ constructor(message) {
+ super(message);
+ this.name = "ValidationError";
+ }
+ toUserMessage() {
+ return `Invalid input: ${this.message}`;
+ }
+}
+
+class ApiError extends Error {
+ constructor(message, statusCode) {
+ super(message);
+ this.name = "ApiError";
+ this.statusCode = statusCode;
+ }
+ toUserMessage() {
+ return `Something went wrong with the request (${this.statusCode}). Try again.`;
+ }
+}
+```
+
+Use `throw` to signal that something went wrong, and `try/catch` with `instanceof` to handle each type:
+
+```js
+async function handleGenerateScreenshot(websiteUrl) {
+ try {
+ if (!websiteUrl || websiteUrl.trim() === "") {
+ throw new ValidationError("URL cannot be empty");
+ }
+
+ const screenshotUrl = await fetchScreenshot(websiteUrl);
+ // ... display the screenshot using screenshotUrl as src
+ } catch (error) {
+ if (error instanceof ValidationError) {
+ // User made a mistake — show a friendly message next to the input
+ showError(error.toUserMessage());
+ } else if (error instanceof ApiError) {
+ // API problem — tell the user to try again
+ showError(error.toUserMessage());
+ } else {
+ // Unexpected error — log it for debugging
+ console.error(error);
+ showError("An unexpected error occurred.");
+ }
+ }
+}
+```
+
+**Where to use error handling in this app:**
+
+- When the user submits the form: validate that the URL field is not empty
+- When calling the screenshot API: catch network failures or non-2xx responses
+- When calling crudcrud (save, load, delete): catch failures and tell the user
+
+---
+
## Optional Tasks/Assignments
> **Note:** Users do not need to be stored in a database or API — just keep them in memory (e.g. an array of instances in your JavaScript). No need to persist them anywhere.
@@ -31,4 +271,6 @@ For the error system, think about what kinds of errors can happen in your app
4. Create another user. When saving a screenshot, also save the user email (or another unique identifier).
5. Make sure you only show screenshots that the logged-in user has uploaded.
-Keep in mind the API key for the website-screenshot and the uuid for crudcrud should be in a secret.js file which is not committed to git.
+---
+
+> Keep in mind the API key for the website-screenshot API and the unique ID for crudcrud should be in a `secret.js` file which is not committed to git.
diff --git a/courses/frontend/advanced-javascript/week4/session-materials/assignment-mockup.svg b/courses/frontend/advanced-javascript/week4/session-materials/assignment-mockup.svg
new file mode 100644
index 00000000..01aba576
--- /dev/null
+++ b/courses/frontend/advanced-javascript/week4/session-materials/assignment-mockup.svg
@@ -0,0 +1,82 @@
+
diff --git a/courses/frontend/advanced-javascript/week4/session-materials/code-inspiration.md b/courses/frontend/advanced-javascript/week4/session-materials/code-inspiration.md
index 4df83aab..e56f640b 100644
--- a/courses/frontend/advanced-javascript/week4/session-materials/code-inspiration.md
+++ b/courses/frontend/advanced-javascript/week4/session-materials/code-inspiration.md
@@ -148,6 +148,118 @@ Promise.all([fetch("/a"), fetch("/b")]);
Promise.race([fetch("/a"), fetch("/b")]);
```
+## Inheritance
+
+A child class inherits all properties and methods from a parent. `extends` = "is-a". `super()` calls the parent's constructor — must come before using `this`.
+
+```js
+class Vehicle {
+ constructor(brand, speed) {
+ this.brand = brand;
+ this.speed = speed;
+ }
+ move() {
+ console.log(`${this.brand} is moving`);
+ }
+}
+
+class Car extends Vehicle {
+ constructor(brand, speed, doors) {
+ super(brand, speed); // calls Vehicle's constructor
+ this.doors = doors;
+ }
+ honk() {
+ console.log("Beep!");
+ }
+}
+
+const car = new Car("Tesla", 0, 4);
+car.move(); // inherited from Vehicle
+car.honk(); // Car's own method
+```
+
+**When inheritance gets awkward** — the child is forced to break or override parent behavior:
+
+```js
+class Vehicle {
+ refuel() {
+ console.log("Filling up the tank...");
+ }
+}
+
+// ElectricCar IS A Vehicle, but refuel() makes no sense for it
+class ElectricCar extends Vehicle {
+ refuel() {
+ throw new Error("I don't use fuel!");
+ }
+}
+```
+
+When you find yourself overriding methods just to disable them, that's a sign to use composition instead.
+
+## Composition
+
+Instead of inheriting behavior, a class HAS parts. Each part is its own class. This is the "has-a" relationship.
+
+```js
+class Engine {
+ start() {
+ console.log("Engine started");
+ }
+}
+
+class GPS {
+ navigate(to) {
+ console.log(`Navigating to ${to}`);
+ }
+}
+
+class Car {
+ constructor(brand) {
+ this.brand = brand;
+ this.engine = new Engine();
+ this.gps = new GPS();
+ }
+ start() {
+ this.engine.start();
+ }
+ goTo(address) {
+ this.gps.navigate(address);
+ }
+}
+```
+
+**Passing dependencies in** — instead of creating the engine inside, receive it from outside. This lets you swap behaviors without changing the class:
+
+```js
+class ElectricEngine {
+ start(brand) {
+ console.log(`${brand}: electric engine humming`);
+ }
+}
+
+class GasEngine {
+ start(brand) {
+ console.log(`${brand}: gas engine roaring`);
+ }
+}
+
+class Car {
+ constructor(brand, engine) {
+ this.brand = brand;
+ this.engine = engine; // passed in from outside
+ }
+ start() {
+ this.engine.start(this.brand);
+ }
+}
+
+new Car("Tesla", new ElectricEngine()).start(); // "Tesla: electric engine humming"
+new Car("Ford", new GasEngine()).start(); // "Ford: gas engine roaring"
+```
+
+**Rule of thumb:** favor composition. Use inheritance only when there's a clear, stable "is-a" relationship.
+
## (Optional) Extending built-ins: Error and Web Components
`Error` is a built-in class; custom errors use `extends` and `super()` like any other subclass. Web Components apply the same “class + lifecycle + HTML” idea to the platform.
@@ -187,3 +299,115 @@ try {
// customElements.define("my-comment", CommentElement);
//
```
+
+## (Optional) Design Patterns
+
+Named solutions to problems that keep showing up. Use these only when they genuinely fit — don't force them.
+
+### Strategy Pattern
+
+Swap behavior by passing in a different object. This is the composition idea taken one step further.
+
+```js
+const electric = {
+ start(b) {
+ console.log(`${b}: humming`);
+ },
+};
+const gas = {
+ start(b) {
+ console.log(`${b}: roaring`);
+ },
+};
+const hybrid = {
+ start(b) {
+ console.log(`${b}: both!`);
+ },
+};
+
+// Same class, different strategy → different behavior
+new Car("Tesla", electric).start();
+new Car("Ford", gas).start();
+new Car("Toyota", hybrid).start();
+```
+
+**When to use it:** multiple ways to do the same thing (sorting, validation, auth); you want to switch behavior without modifying the class itself.
+
+### Factory Pattern
+
+A function that creates objects for you — hides `new` and setup logic from the caller.
+
+```js
+function createCar(type, brand) {
+ const engines = {
+ electric: {
+ start(b) {
+ console.log(`${b}: humming`);
+ },
+ },
+ gas: {
+ start(b) {
+ console.log(`${b}: roaring`);
+ },
+ },
+ };
+ return new Car(brand, engines[type]);
+}
+
+const tesla = createCar("electric", "Tesla");
+const ford = createCar("gas", "Ford");
+```
+
+**When to use it:** object creation is complex (many params, config, dependencies); you want to centralize and hide construction details.
+
+### Observer Pattern
+
+"When something happens, notify everyone who cares." This is how `addEventListener`, Node's `EventEmitter`, and most UI frameworks work under the hood.
+
+```js
+class Order {
+ constructor() {
+ this.listeners = [];
+ this.status = "pending";
+ }
+
+ onChange(fn) {
+ this.listeners.push(fn);
+ }
+
+ updateStatus(newStatus) {
+ this.status = newStatus;
+ this.listeners.forEach((fn) => fn(this.status));
+ }
+}
+
+const order = new Order();
+order.onChange((s) => console.log(`Customer notified: ${s}`));
+order.onChange((s) => console.log(`Driver notified: ${s}`));
+order.updateStatus("ready"); // both callbacks fire
+```
+
+### Singleton Pattern
+
+Only one instance ever exists. Every call to `new` returns the same object.
+
+```js
+class Database {
+ constructor(url) {
+ if (Database.instance) return Database.instance;
+ this.url = url;
+ this.connected = false;
+ Database.instance = this;
+ }
+ connect() {
+ this.connected = true;
+ }
+}
+
+const db1 = new Database("postgres://...");
+const db2 = new Database("mysql://...");
+console.log(db1 === db2); // true — same instance!
+```
+
+**Good for:** DB connections, config, logging, caches — things you truly need only one of.
+**Use sparingly:** singletons are global state in disguise. They make testing harder and hide dependencies.
diff --git a/courses/frontend/advanced-javascript/week4/session-materials/exercises.md b/courses/frontend/advanced-javascript/week4/session-materials/exercises.md
index a375086f..64c32d47 100644
--- a/courses/frontend/advanced-javascript/week4/session-materials/exercises.md
+++ b/courses/frontend/advanced-javascript/week4/session-materials/exercises.md
@@ -2,17 +2,19 @@
Work through these in order.
-## 1. Create a user class
+## 1. User class with DOM rendering
-The class should have 2 properties: `firstName` and `lastName`. Hint: Use `this` and `constructor`.
+### 1. Create a user class
-## 2. Create an instance of the class
+Create a `User` class with 2 properties: `firstName` and `lastName`. Hint: use `this` and `constructor`.
+
+### 2. Create an instance of the class
Use the `new` keyword and assign the instance in a variable.
Add a **`renderUserCard(user)`** function that accepts a **`User`** instance and renders a user card on the page (e.g. a `div` with `firstName` and `lastName`).
-## 3. Create a class method
+### 3. Create a class method
1. Add **`getFullName`**: it should return the combined first and last name of the user. Use string concatenation or template literals and **`this`** to read the properties.
@@ -20,7 +22,7 @@ Add a **`renderUserCard(user)`** function that accepts a **`User`** instance and
3. Call **`myUser.render()`** so the card appears on the page (you can stop using **`renderUserCard`** once this works).
-## 4. Creating a CV class
+## 2. Creating a CV class
The CV that we will be making uses three classes: `Job`, `Education` and
`CV`. The `CV` class we have made for you (with some missing functionality). The `Job` and `Education` classes you need to create.
@@ -87,3 +89,53 @@ class CV {
### Part 4
Add a method to the `CV` class called `renderCV()`. This method should render out the CV using HTML. Make sure, that view updates, when data is changed.
+
+## 3. Design Challenge: FoodDash
+
+You're building a food delivery app. Customers browse restaurants, add items to their order, and a driver picks it up and delivers it.
+
+**Rules:** paper only — no code yet!
+
+For each class you identify, write down:
+
+- Its name
+- Its properties
+- Its methods
+- How it relates to the other classes
+
+Think about:
+
+1. What classes do you need?
+2. What properties does each class have?
+3. What methods does each class need?
+4. How do the classes relate to each other?
+5. Does anything share behavior? How would you handle that?
+
+When done, compare your design with others: which classes did different people pick? Did anyone make `Driver extends User`? How did you handle the `Order`/`Restaurant` relationship?
+
+## Bonus: Build FoodDash
+
+Now that you've designed FoodDash on paper, build it in code.
+
+1. Create a `Restaurant` class with a `name` property and a `menu` property (array of items, each with a `name` and `price`).
+2. Create an `Order` class that takes an array of items and a `Restaurant` instance.
+ - Add an `addItem(item)` method and a `removeItem(item)` method.
+ - Add an `async calculateTotal()` method that sums the prices of all items in the order.
+3. Create a `User` class that receives a `name`, `email`, and `role` object via the constructor. The role object must have a `perform(name)` method.
+ - Add a `doWork()` method that calls `this.role.perform(this.name)`.
+4. Create two role objects (`customerRole` and `driverRole`), each with a `perform(name)` method that logs what that role does.
+
+**Bonus:** Add a `static Order.sortByTotal(orders)` method that sorts an array of orders by total price.
+
+## Challenge: Monster Arena
+
+A turn-based monster battle game — design and OOP in action.
+
+You have a starter project in [`./oop-monster-arena/`](./oop-monster-arena/). Follow the instructions in its README.
+
+The challenge covers:
+
+- Modeling game entities as classes (`Monster`, `Arena`, `Attack`)
+- Using composition to give monsters different attack strategies
+- Inheritance for shared monster behavior
+- Turn-based game loop logic
diff --git a/courses/frontend/advanced-javascript/week4/session-materials/js_oop_classes.pdf b/courses/frontend/advanced-javascript/week4/session-materials/js_oop_classes.pdf
new file mode 100644
index 00000000..0e566337
Binary files /dev/null and b/courses/frontend/advanced-javascript/week4/session-materials/js_oop_classes.pdf differ
diff --git a/courses/frontend/advanced-javascript/week4/session-materials/oop-monster-arena/.gitignore b/courses/frontend/advanced-javascript/week4/session-materials/oop-monster-arena/.gitignore
new file mode 100644
index 00000000..62ad6bc9
--- /dev/null
+++ b/courses/frontend/advanced-javascript/week4/session-materials/oop-monster-arena/.gitignore
@@ -0,0 +1,4 @@
+.claude
+node_modules
+dist
+docs
\ No newline at end of file
diff --git a/courses/frontend/advanced-javascript/week4/session-materials/oop-monster-arena/ABILITY_EXAMPLES.md b/courses/frontend/advanced-javascript/week4/session-materials/oop-monster-arena/ABILITY_EXAMPLES.md
new file mode 100644
index 00000000..d29150ac
--- /dev/null
+++ b/courses/frontend/advanced-javascript/week4/session-materials/oop-monster-arena/ABILITY_EXAMPLES.md
@@ -0,0 +1,158 @@
+# Ability Examples
+
+A reference of ability ideas you can build from.
+
+---
+
+## The two rules to remember
+
+> **Higher `amount` = lower trigger chance.** The budget is fixed, so if you return `50`, it fires ~30% of the time. If you return `10`, it fires ~100% (or 80% for HealAbility). Big swings are rare; small effects are reliable.
+
+> **Fewer charges = higher trigger chance.** Limited-charge abilities get a budget bonus to compensate. A 1-charge ability fires up to 5× more reliably than an unlimited one at the same amount — so a single-use nuke can be near-guaranteed.
+
+---
+
+## Budget & trigger chance
+
+Every ability type has a fixed **budget**. The trigger chance is calculated automatically:
+
+```text
+triggerChance = (budget × chargeMultiplier) / amount (capped at 100%)
+```
+
+### Base budgets (unlimited / ≥ 5 charges)
+
+| Type | Budget | amount 8 | amount 15 | amount 20 | amount 30 | amount 50 |
+| ------------- | ------ | -------- | --------- | --------- | --------- | --------- |
+| DamageAbility | 15 | 100% | 100% | 75% | 50% | 30% |
+| HealAbility | 12 | 100% | 80% | 60% | 40% | 24% |
+| ArmorAbility | 8 | 100% | 53% | 40% | 27% | 16% |
+
+### Charge multiplier
+
+Charges are part of the budget. An ability with limited charges can fire fewer times per bout, so each trigger is worth more — the budget scales up automatically.
+
+```text
+chargeMultiplier = 5 / min(charges, 5)
+```
+
+| charges | multiplier | effective DamageAbility budget |
+| ------- | ---------- | ------------------------------ |
+| ∞ or ≥5 | ×1.0 | 15 |
+| 3 | ×1.67 | 25 |
+| 2 | ×2.5 | 37.5 |
+| 1 | ×5.0 | 75 |
+
+**Examples with charges:**
+
+- `new DamageAbility(30)` → 50% trigger, fires every turn it can (unlimited)
+- `new DamageAbility(30, 3)` → **83%** trigger, fires at most 3 times
+- `new DamageAbility(30, 1)` → **100%** trigger, fires exactly once (guaranteed)
+- `new HealAbility(20)` → 60% trigger, unlimited
+- `new HealAbility(20, 2)` → **100%** trigger, fires at most twice
+- `new ArmorAbility(20)` → 40% trigger, unlimited
+- `new ArmorAbility(20, 1)` → **100%** trigger, fires exactly once (guaranteed)
+
+When `activate()` returns a **variable** amount, the charge multiplier still applies and the chance is recalculated each turn based on whatever value you return.
+
+---
+
+## DamageAbility
+
+Deals extra damage to the opponent each time it triggers.
+
+**Flat bonus** — simplest case, always hits for the same extra damage.
+
+```text
+activate() returns 20 — triggers 75% of the time.
+```
+
+**Rage** — tracks how many hits you've taken; the more damage received, the harder the next hit.
+
+```text
+activate() returns 10 + (hitsReceived × 5).
+Each hit you absorb adds 5 to the next ability trigger.
+describe() says "Warlord retaliates with X fury damage!"
+```
+
+**Finishing blow** — checks opponent's HP; explodes when they're low.
+
+```text
+activate() returns 50 if opponent.hp.current < 30, else 10.
+When the enemy is near death, you deal a huge spike.
+```
+
+**Berserk (limited charges)** — 3 charges only, but each one hits hard.
+
+```text
+new DamageAbility(40, 3) — triggers ~63% each turn (budget ×1.67), 3 uses total.
+```
+
+---
+
+## HealAbility
+
+Restores HP to the attacker each time it triggers.
+
+**Steady regeneration** — modest heal every few turns.
+
+```text
+new HealAbility(15) — heals 15 HP, triggers 80% of the time.
+```
+
+**Desperate surge** — heals more when critically low.
+
+```text
+activate() returns 40 if attacker.hp.current < 20, else 8.
+Nearly dead? Panic-heal for a large burst.
+```
+
+**Vampiric strike** — heals based on how hard you attack.
+
+```text
+activate() returns attacker.attackPower / 2.
+Steals life proportional to your own strength.
+```
+
+---
+
+## ArmorAbility
+
+Reduces the opponent's `attackPower` permanently for the rest of the bout.
+
+**Steady debuff** — grinds down the enemy's attack over time.
+
+```text
+new ArmorAbility(5) — reduces opponent attack by 5, triggers 100% of the time.
+By turn 4 the enemy hits much weaker.
+```
+
+**Intimidation (one shot)** — big one-time armor shred on first contact.
+
+```text
+new ArmorAbility(20, 1) — 1 charge, reduces attack by 20 (100% guaranteed, budget ×5).
+One scary moment early that sets the tone for the whole fight.
+```
+
+---
+
+## Ability + onTakeDamage (ability swap)
+
+The most advanced pattern — your monster **changes ability mid-fight** when hurt.
+
+**Cornered animal** — starts defensive, switches to offense when badly hurt.
+
+```text
+Start with a HealAbility.
+In onTakeDamage(): when HP drops below 50%, swap this.ability to a DamageAbility.
+describe() says "Cornered, X fights back with desperation!"
+```
+
+**Growing counter** — tracks damage taken and scales the ability accordingly.
+
+```text
+this.hitsReceived = 0
+onTakeDamage() increments hitsReceived
+activate() returns hitsReceived × 3
+The ability grows stronger the longer the fight goes on.
+```
diff --git a/courses/frontend/advanced-javascript/week4/session-materials/oop-monster-arena/README.md b/courses/frontend/advanced-javascript/week4/session-materials/oop-monster-arena/README.md
new file mode 100644
index 00000000..a5ceef56
--- /dev/null
+++ b/courses/frontend/advanced-javascript/week4/session-materials/oop-monster-arena/README.md
@@ -0,0 +1,156 @@
+# Monster Arena ⚔️
+
+_Created by Paolo Bozzini for HackYourFuture Denmark — modified fork from [PaoloBozzini/oop-monster-arena](https://github.com/PaoloBozzini/oop-monster-arena/tree/main)._
+
+Each group builds a custom monster. At the end of the session, **all monsters fight in a round-robin tournament** — every monster vs every other monster, once. The arena animates each battle in real time in the browser, with a live results matrix and a 1 000-simulation Monte Carlo win-rate chart.
+
+---
+
+## What you'll build
+
+A JavaScript class that extends `Monster`. Your monster has:
+
+- A **name**, **health points**, and **attack power** (you choose the numbers)
+- A **special ability** — something creative that fires after each normal attack
+
+The base `Monster` class uses a `HealthComponent` for HP management — this is an example of **composition** (has-a relationship). Your subclass is an example of **inheritance** (is-a relationship). You'll see both OOP patterns in action.
+
+**Stat budget:** your combined score — `health + attackPower × 3` — must be ≤ 300. Attack power is more expensive than raw HP (3 pts each vs 1 pt). Dragon uses 160 HP + 30 attack × 3 = 250 pts. Exceeding the budget throws an error immediately so you can fix it fast.
+
+---
+
+## Your workflow
+
+### 1. Get the project
+
+Clone or download this project, then create your own GitHub repository and push it there:
+
+```bash
+cd oop-monster-arena
+git remote set-url origin
+git push -u origin main
+npm install
+```
+
+### 2. Copy the template
+
+```bash
+cp src/monsters/your-monster.js src/monsters/YourMonsterName.js
+```
+
+Open the new file. Read `src/monsters/Dragon.js` for a full worked example, then fill in your stats and pick an ability to inject.
+
+### 3. Add your monster's image
+
+- Find or generate an image for your monster (Google Images, DALL·E, Midjourney…)
+- The filename **must exactly match your class name** (case-sensitive!)
+- If your image is a **PNG**: save it as `assets/monsters/YourMonsterName.png` — done!
+- If your image is a **JPG or other format**: save it with the right extension, then add this to your class:
+ ```js
+ get imagePath() { return 'assets/monsters/YourMonsterName.jpg'; }
+ ```
+
+### 4. Test your monster
+
+```bash
+npm test # tests src/monsters/your-monster.js
+npm test src/monsters/YourMonsterName.js # tests your renamed file
+```
+
+You should see green checks for all tests. Fix anything red before moving on.
+
+---
+
+## What you write
+
+Two classes in one file — an ability and a monster:
+
+```js
+// Your ability — extends one of the three base types
+class MyAbility extends DamageAbility {
+ activate(attacker, opponent) {
+ /* return damage amount */
+ }
+ describe(attacker, amount) {
+ /* return a log string */
+ }
+}
+
+// Your monster — extends Monster, injects your ability
+export class YourMonster extends Monster {
+ constructor() {
+ super("Name", health, attackPower, new MyAbility(amount));
+ }
+ onTakeDamage(amount) {
+ /* optional — react when hit, swap stats or ability */
+ }
+}
+```
+
+## Monster hooks
+
+| Override | When it's called | What to do |
+| ---------------------- | -------------------------- | -------------------------------------------- |
+| `onTakeDamage(amount)` | Every time you take damage | Change stats, swap ability — no return value |
+
+## Ability base types
+
+Extend one and override `activate()`. `triggerChance` is derived automatically as `budget / amount` — you never set it directly.
+
+| Base type | Budget | Default effect |
+| --------------- | ------ | ------------------------------------- |
+| `DamageAbility` | 15 | `opponent.takeDamage(this.amount)` |
+| `HealAbility` | 12 | `attacker.hp.heal(this.amount)` |
+| `ArmorAbility` | 8 | `opponent.attackPower -= this.amount` |
+
+Example: `new MyAbility(30)` extends `DamageAbility` → triggerChance = 15/30 = 50%.
+
+See `ABILITY_EXAMPLES.md` for full examples including the charge system.
+
+---
+
+## Study guide
+
+| File | Read it? | Edit it? |
+| ------------------------------ | -------------------------------------- | ------------------------ |
+| `src/core/health.js` | ✅ Yes — see how **composition** works | ❌ No |
+| `src/core/monster.js` | ✅ Yes — understand the base class | ❌ No |
+| `src/core/ability.js` | ✅ Yes — see the three ability types | ❌ No |
+| `src/monsters/Dragon.js` | ✅ Yes — your **reference example** | ❌ No |
+| `src/monsters/your-monster.js` | ✅ Yes | ✅ **This is your file** |
+| `src/arena.js` | Optional | ❌ No |
+| `src/ui.js` | Optional | ❌ No |
+
+---
+
+## Running the arena
+
+```bash
+npm install
+npm run dev
+```
+
+Open the URL shown in the terminal. To add more monsters to the tournament, update `src/main.js`:
+
+```js
+// Step 1: add an import at the top
+import { Hydra } from "./monsters/Hydra.js";
+import { Werewolf } from "./monsters/Werewolf.js";
+
+// Step 2: add a new instance to the array
+const monsters = [new Dragon(), new Hydra(), new Werewolf()];
+```
+
+Vite hot-reloads automatically — save `main.js` and the browser updates instantly.
+
+---
+
+## Checklist
+
+- [ ] Cloned/downloaded the project, created own repo, ran `npm install`
+- [ ] Copied and renamed `your-monster.js`
+- [ ] Renamed the class to match the filename (case-sensitive!)
+- [ ] Called `super()` with name, health, attack power, and an ability (budget: `health + attackPower × 3 ≤ 300`)
+- [ ] Passed a `DamageAbility`, `HealAbility`, or `ArmorAbility` as the 4th argument
+- [ ] Added an image to `assets/monsters/` (exact class name as filename)
+- [ ] Ran `npm test` — all checks pass
diff --git a/courses/frontend/advanced-javascript/week4/session-materials/oop-monster-arena/assets/monsters/.gitkeep b/courses/frontend/advanced-javascript/week4/session-materials/oop-monster-arena/assets/monsters/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/courses/frontend/advanced-javascript/week4/session-materials/oop-monster-arena/assets/monsters/Dragon.svg b/courses/frontend/advanced-javascript/week4/session-materials/oop-monster-arena/assets/monsters/Dragon.svg
new file mode 100644
index 00000000..b127eaa8
--- /dev/null
+++ b/courses/frontend/advanced-javascript/week4/session-materials/oop-monster-arena/assets/monsters/Dragon.svg
@@ -0,0 +1,37 @@
+
diff --git a/courses/frontend/advanced-javascript/week4/session-materials/oop-monster-arena/assets/monsters/Goblin.svg b/courses/frontend/advanced-javascript/week4/session-materials/oop-monster-arena/assets/monsters/Goblin.svg
new file mode 100644
index 00000000..c923c1ab
--- /dev/null
+++ b/courses/frontend/advanced-javascript/week4/session-materials/oop-monster-arena/assets/monsters/Goblin.svg
@@ -0,0 +1,71 @@
+
diff --git a/courses/frontend/advanced-javascript/week4/session-materials/oop-monster-arena/assets/monsters/Troll.svg b/courses/frontend/advanced-javascript/week4/session-materials/oop-monster-arena/assets/monsters/Troll.svg
new file mode 100644
index 00000000..d8578be1
--- /dev/null
+++ b/courses/frontend/advanced-javascript/week4/session-materials/oop-monster-arena/assets/monsters/Troll.svg
@@ -0,0 +1,89 @@
+
diff --git a/courses/frontend/advanced-javascript/week4/session-materials/oop-monster-arena/index.html b/courses/frontend/advanced-javascript/week4/session-materials/oop-monster-arena/index.html
new file mode 100644
index 00000000..7b6150ee
--- /dev/null
+++ b/courses/frontend/advanced-javascript/week4/session-materials/oop-monster-arena/index.html
@@ -0,0 +1,124 @@
+
+
+
+
+
+
+
+
+ Monster Arena
+
+
+
+
`;
+ list.appendChild(li);
+
+ requestAnimationFrame(() => {
+ li.querySelector(".mc-bar-fill").style.width = `${pct}%`;
+ });
+ });
+}
diff --git a/courses/frontend/advanced-javascript/week4/session-materials/oop-monster-arena/test.js b/courses/frontend/advanced-javascript/week4/session-materials/oop-monster-arena/test.js
new file mode 100644
index 00000000..0422f294
--- /dev/null
+++ b/courses/frontend/advanced-javascript/week4/session-materials/oop-monster-arena/test.js
@@ -0,0 +1,207 @@
+// test.js
+//
+// Quick sanity-check for your monster class.
+//
+// Usage:
+// npm test ← tests src/monsters/your-monster.js
+// npm test src/monsters/Hydra.js ← tests your renamed file
+
+import path from "path";
+import { fileURLToPath } from "url";
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+
+// ── Which file to test? ──
+const target = process.argv[2] ?? "src/monsters/your-monster.js";
+const filePath = path.resolve(__dirname, target);
+
+// ── Output helpers ──
+const green = (s) => `\x1b[32m${s}\x1b[0m`;
+const red = (s) => `\x1b[31m${s}\x1b[0m`;
+const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
+const bold = (s) => `\x1b[1m${s}\x1b[0m`;
+
+let passed = 0;
+let failed = 0;
+
+function ok(label) {
+ console.log(` ${green("✓")} ${label}`);
+ passed++;
+}
+
+function fail(label, reason) {
+ console.log(` ${red("✗")} ${label}`);
+ console.log(` ${red("→")} ${reason}`);
+ failed++;
+}
+
+// ── Load monster module ──
+console.log(`\n${bold("Monster Arena — Test Runner")}`);
+console.log(`Testing: ${yellow(target)}\n`);
+
+let MonsterClass;
+try {
+ const mod = await import(filePath);
+ const exports = Object.values(mod).filter((v) => typeof v === "function");
+ if (exports.length === 0) {
+ console.log(
+ red("✗ No exported class found — did you forget `export class ...`?"),
+ );
+ process.exit(1);
+ }
+ MonsterClass = exports[0];
+ ok(`File loads without syntax errors`);
+} catch (err) {
+ console.log(red(`✗ Failed to load ${target}:`));
+ console.log(` ${err.message}`);
+ process.exit(1);
+}
+
+// ── Instantiation + stat budget ──
+let monster;
+try {
+ monster = new MonsterClass();
+ ok(`new ${MonsterClass.name}() constructs without error`);
+ const score = monster.hp.max + monster.attackPower * 3;
+ const abilityNote = monster.ability
+ ? ` | ability: ${monster.ability.constructor.name}(${monster.ability.amount}), triggerChance: ${(monster.ability.triggerChance * 100).toFixed(0)}%`
+ : " | no ability";
+ ok(
+ `Stat budget: ${monster.hp.max} HP + ${monster.attackPower} atk × 3 = ${score}/300${abilityNote}`,
+ );
+} catch (err) {
+ fail(`Constructor throws an error`, err.message);
+ console.log(`\n${red("Fix the error above before testing further.")}`);
+ process.exit(1);
+}
+
+// ── Basic properties ──
+if (typeof monster.name === "string" && monster.name.length > 0) {
+ ok(`name is a non-empty string: "${monster.name}"`);
+} else {
+ fail(
+ `name must be a non-empty string`,
+ `got: ${JSON.stringify(monster.name)}`,
+ );
+}
+
+if (typeof monster.attackPower === "number" && monster.attackPower >= 1) {
+ ok(`attackPower is a number ≥ 1: ${monster.attackPower}`);
+} else {
+ fail(`attackPower must be a number ≥ 1`, `got: ${monster.attackPower}`);
+}
+
+if (monster.isAlive()) {
+ ok(`isAlive() returns true at full HP`);
+} else {
+ fail(
+ `isAlive() should return true at start`,
+ `returned false — health may be 0?`,
+ );
+}
+
+// ── attack() shape ──
+const dummy = new MonsterClass(); // opponent
+let attackResult;
+try {
+ attackResult = monster.attack(dummy);
+ if (typeof attackResult?.damage === "number" && attackResult.damage >= 1) {
+ ok(
+ `attack() returns { damage: ${attackResult.damage}, special: ${JSON.stringify(attackResult.special)} }`,
+ );
+ } else {
+ fail(
+ `attack() must return { damage: number, ... }`,
+ `got: ${JSON.stringify(attackResult)}`,
+ );
+ }
+} catch (err) {
+ fail(`attack() threw an error`, err.message);
+}
+
+// ── ability shape ──
+monster.reset();
+dummy.reset();
+if (monster.ability === null) {
+ ok(`ability is null (no ability injected)`);
+} else {
+ const { Ability } = await import(
+ path.resolve(__dirname, "src/core/ability.js")
+ );
+ if (monster.ability instanceof Ability) {
+ const chance = (monster.ability.triggerChance * 100).toFixed(0);
+ ok(
+ `ability is a ${monster.ability.constructor.name} instance (triggerChance: ${chance}%)`,
+ );
+ } else {
+ fail(
+ `ability must be a DamageAbility, HealAbility, or ArmorAbility`,
+ `got: ${monster.ability}`,
+ );
+ }
+ try {
+ const result = monster.ability.tryActivate(monster, dummy);
+ if (result === null || typeof result === "string") {
+ ok(
+ `ability.tryActivate() returns ${result === null ? "null (did not trigger this roll)" : `a string`}`,
+ );
+ } else {
+ fail(
+ `ability.tryActivate() must return a string or null`,
+ `got: ${JSON.stringify(result)}`,
+ );
+ }
+ } catch (err) {
+ fail(`ability.tryActivate() threw an error`, err.message);
+ }
+}
+
+// ── reset() restores HP ──
+monster.reset();
+monster.hp.takeDamage(50);
+const hpBefore = monster.health;
+monster.reset();
+if (monster.health === monster.hp.max && monster.health > hpBefore) {
+ ok(`reset() fully restores HP (${hpBefore} → ${monster.health})`);
+} else if (monster.health === monster.hp.max) {
+ ok(
+ `reset() restores HP to max (was already full or took no damage — verify manually)`,
+ );
+} else {
+ fail(
+ `reset() did not restore HP to max`,
+ `expected ${monster.hp.max}, got ${monster.health}`,
+ );
+}
+
+// ── Monster can die ──
+monster.reset();
+const target2 = new MonsterClass();
+let turns = 0;
+while (target2.isAlive() && turns < 500) {
+ monster.attack(target2);
+ turns++;
+}
+if (!target2.isAlive()) {
+ ok(`Monster can die (took ${turns} attacks)`);
+} else {
+ fail(
+ `Monster never died after 500 attacks`,
+ `check that takeDamage() reduces HP`,
+ );
+}
+
+// ── Summary ──
+console.log("");
+if (failed === 0) {
+ console.log(green(bold(`All ${passed} checks passed! 🎉`)));
+ console.log(
+ `\n${yellow("Next step:")} add your monster to src/main.js and run ${yellow("npm run dev")} to see it fight!\n`,
+ );
+} else {
+ console.log(red(bold(`${failed} check(s) failed, ${passed} passed.`)));
+ console.log(
+ `\nFix the issues above, then run ${yellow("npm test")} again.\n`,
+ );
+ process.exit(1);
+}
diff --git a/courses/frontend/advanced-javascript/week4/session-plan.md b/courses/frontend/advanced-javascript/week4/session-plan.md
index 977f9d03..010a6069 100644
--- a/courses/frontend/advanced-javascript/week4/session-plan.md
+++ b/courses/frontend/advanced-javascript/week4/session-plan.md
@@ -2,29 +2,103 @@
## Session Materials
+- [Slides](./session-materials/js_oop_classes.pdf) – 41-slide deck covering all topics below
- [Demo](./session-materials/demo/) – In-session live coding: plain-object motivation, `Comment` class, methods, flagged comments, then Errors / Web Components as “real world” context. **index.js** = worksheet; **index-solution.js** = reference. [README](./session-materials/demo/README.md).
+- [Code inspiration](./session-materials/code-inspiration.md) – Snippets for the board or live coding
## Session Outline
-
-
-Start VERY simple. Just a class that has few fields, no methods. Explain the diff from object to class. Explain instance etc. When they get that move on to class methods. **Only teach extends if they really are on top of things** otherwise just get them comfortable with classes :) if you can repeat a bit of promise, maybe when working with class that would be great.
-
-- Constructor
- - [Code inspiration](./session-materials/code-inspiration.md#constructor)
- - [Exercise](./session-materials/exercises.md#1-create-a-user-class)
-- Instance
- - [Code inspiration](./session-materials/code-inspiration.md#instance)
- - [Exercise](./session-materials/exercises.md#2-create-an-instance-of-the-class)
-- Methods (instance + static)
- - [Code inspiration](./session-materials/code-inspiration.md#methods)
- - [Code inspiration — static methods](./session-materials/code-inspiration.md#static-methods) (Promise as "you already use this")
- - [Exercise](./session-materials/exercises.md#3-create-a-class-method)
-- `this`
- - Refers to the instance of the class. Do go into too much detail and edge cases. Avoid mentioning `bind`, `apply`, etc unless you find it super important, the trainees will just forget it anyway!
-- [Exercise](./session-materials/exercises.md#4-creating-a-cv-class)
-- Extend (Only if time!)
- - [Code inspiration](./session-materials/code-inspiration.md#extending-built-ins-error-and-web-components) (`Error`, `ValidationError`, Web Components sketch — matches demo Part 4)
+Start VERY simple — build the mental model before touching code. **Only teach Inheritance & Composition if trainees are solid on classes and methods.** It's the last major section and can be cut if needed. Design Patterns (slides 35–41) are optional/bonus only.
+
+### 1. The Mental Model
+
+Concepts before code. Get this right and the syntax will feel natural.
+
+- **Class vs Instance:** class = blueprint (the concept), instance = the real thing
+ - “Car” the concept vs that red Tesla parked outside
+ - One class → many instances, each with its own data
+- **Properties, Methods & Static:**
+ - Properties = what it IS — the data that makes each instance unique
+ - Methods = what it DOES — behaviors that use or change properties
+ - Static = shared truths, same for ALL instances (not specific to one)
+- Show the full picture (slide 6): blueprint on the left → two instances on the right
+
+### 2. From Object to Class
+
+Bridge from what trainees already know.
+
+- Start with an object literal: `const myCar = { brand: “Tesla”, drive() { ... } }` — fine for ONE car
+- Problem: what if you need 50? Copy-pasting 50 objects is a nightmare → you need a blueprint → a class
+- Show the class syntax: `class`, `constructor`, `this`
+ - `class` keyword defines the blueprint
+ - `constructor` runs automatically when you call `new`
+ - `this.___` assigns properties to the new instance
+- What `new` does step by step (slide 10): creates empty object → sets `this` → runs constructor → returns object
+ - [Code inspiration — constructor](./session-materials/code-inspiration.md#constructor)
+ - [Code inspiration — instance](./session-materials/code-inspiration.md#instance)
+- **[Exercise 1: User class with DOM rendering](./session-materials/exercises.md#1-user-class-with-dom-rendering)** — parts 1 & 2 (create class + instantiate)
+
+### 3. Methods & `this`
+
+- Adding methods: functions defined inside the class, no `function` keyword needed
+- **`this` = the thing left of the dot** — that's all they need to know
+ - `tesla.drive(100)` → `this` is `tesla`; `honda.drive(60)` → `this` is `honda`
+ - Same method, different instance → different `this` → different result
+ - Avoid going into edge cases (`bind`, `apply`, `call`) — trainees will forget it immediately
+- Methods calling other methods via `this.method()`
+- Async methods: just add the `async` keyword — works exactly like regular async functions, returns a Promise
+- Static methods & properties: belong to the class itself, not any instance
+ - Called on the class name: `Car.numberOfWheels` ✓ vs `myCar.numberOfWheels` ✗
+ - Bridge to Promises: `Promise.resolve()`, `Promise.all()` — they already use static methods!
+ - [Code inspiration — static methods](./session-materials/code-inspiration.md#static-methods)
+- [Code inspiration — methods](./session-materials/code-inspiration.md#methods)
+- **[Exercise 1: User class with DOM rendering](./session-materials/exercises.md#1-user-class-with-dom-rendering)** — part 3 (add `getFullName` + `render()`)
+- **[Exercise 2: Creating a CV class](./session-materials/exercises.md#2-creating-a-cv-class)**
+
+### 4. Design Challenge: FoodDash
+
+Paper only — no code. This builds design thinking and sets up the inheritance discussion.
+
+- **Brief:** food delivery app — customers browse restaurants, add items, driver delivers
+- **Rules:** paper only; for each class: name + properties + methods; show relationships between classes
+- 8 minutes in small groups, then each group presents their design
+- Discussion questions to guide debrief (slide 21):
+ - Which classes did different teams create?
+ - Did anyone make `Driver extends User`? `Customer extends User`?
+ - How did you handle the relationship between `Order` and `Restaurant`?
+ - Where did shared behavior come up? How did you handle it?
+- That last question leads naturally into the next section
+- **[Exercise 3: Design Challenge: FoodDash](./session-materials/exercises.md#3-design-challenge-fooddash)**
+
+### 5. Inheritance & Composition
+
+**Only teach this section if trainees are solid on classes and methods.**
+
+- **Inheritance: `extends` and `super()`**
+ - A child class IS A type of the parent — gets all parent properties and methods
+ - `super(...)` calls the parent's constructor — must come before using `this`
+ - `extends` = “is-a”: a Car IS A Vehicle
+ - [Code inspiration — inheritance](./session-materials/code-inspiration.md#inheritance)
+- **When inheritance works:** clean “is-a” relationship, stable parent, max one level deep
+ - Good examples: `Vehicle → Car, Truck`; `Shape → Circle, Rectangle`; `Account → CheckingAccount, SavingsAccount`
+- **When inheritance gets awkward:** `ElectricCar extends Vehicle` — forced to override `refuel()` with an error
+ - Adding `charge()` to `Vehicle` breaks gas cars; overriding to disable is a code smell
+- **Composition: “has-a” instead of “is-a”**
+ - Car HAS an Engine, HAS a GPS — create parts as classes and delegate to them
+ - [Code inspiration — composition](./session-materials/code-inspiration.md#composition)
+- **Passing dependencies in:** inject the engine from outside — swap behaviors without changing the class
+- **Back to FoodDash:** inheritance approach hits a wall (a driver who is also a customer — JS has no multiple inheritance); composition handles it cleanly
+- **Rule of thumb:** favor composition; use inheritance only when there's a clear, stable “is-a” relationship
+
+### 6. Optional: Design Patterns
+
+Bonus only — skip unless there is plenty of time and trainees are engaged.
+
+- **Strategy:** swap behavior by passing in a different object (the composition idea taken further)
+- **Factory:** a function that hides `new` and setup details from the caller
+- **Observer:** “when something happens, notify everyone who cares” — think `addEventListener`; how DOM events, Node EventEmitter, and most UI frameworks work
+- **Singleton:** only one instance ever exists — useful for DB connections or config, but global state in disguise, use sparingly
+- **[Bonus Exercise: Build FoodDash](./session-materials/exercises.md#bonus-build-fooddash)**
## Exercises