Advanced Challenges
Objectives
This chapter introduces no new concepts. Each challenge below combines several ideas from across the course: classes, closures, recursion, and error handling, in one program.
Challenge 1: A Stack Class
Write a class Stack with a private field holding its items, and these methods: push(item) (adds to the top), pop() (removes and returns the top item), peek() (returns the top item without removing it), and isEmpty() (returns true or false). Also write a custom error class StackEmptyError (extending Error), and have pop() and peek() throw it when the stack is empty instead of returning undefined.
Example:
const stack = new Stack();
stack.push(1);
stack.push(2);
stack.push(3);
stack.pop(); // 3
stack.peek(); // 2
stack.isEmpty(); // false
stack.pop();
stack.pop();
stack.pop(); // throws StackEmptyError: Cannot pop from an empty stack
Hint: a plain array’s .push() and .pop() already behave like a stack’s top. Your class can lean on that internally, the point of the exercise is the class wrapper, the private field, and the custom error, not reinventing array storage.
Challenge 2: Search a Nested Object Recursively
Write a function deepFind(obj, key) that searches an object for a given property name, no matter how deeply it’s nested inside other objects, and returns its value. If the key isn’t found anywhere, return undefined.
Example:
const company = {
name: "Acme",
address: {
city: "Nairobi",
geo: { lat: -1.29, lng: 36.82 }
}
};
deepFind(company, "lat"); // -1.29
deepFind(company, "missingKey"); // undefined
Hint: check whether the key exists directly on the current object first. If it doesn’t, you need to recurse into each of that object’s own values that are themselves objects, and search those the same way.
Challenge 3: A Small Event Emitter
Write a class EventEmitter with two methods: on(event, callback), which registers a callback function to run when a named event happens, and emit(event, ...args), which runs every callback registered for that event, passing along any extra arguments. The same event name can have more than one callback registered.
Example:
const emitter = new EventEmitter();
emitter.on("greet", (name) => console.log(`Hello, ${name}!`));
emitter.on("greet", (name) => console.log(`Hi there, ${name}!`));
emitter.emit("greet", "Maya");
// Hello, Maya!
// Hi there, Maya!
Hint: you need to store, per event name, an array of callback functions. A private object field where each key is an event name and each value is an array of callbacks handles this cleanly. This exact pattern, registering functions to run later when something happens, is what you’ll see again very soon under a different name: events.
Challenge 4: Retry a Flaky Function
Write a function attempt(fn, retries) that calls fn. If fn throws, catch the error and try again, up to retries total attempts. If fn succeeds on any attempt, return its result immediately and stop retrying. If every attempt fails, throw a custom error MaxRetriesExceededError whose message includes how many attempts were made and what the last error said.
Example:
function makeFlaky(failTimes) {
let calls = 0;
return function () {
calls++;
if (calls <= failTimes) {
throw new Error(`attempt ${calls} failed`);
}
return "success";
};
}
const flakyTwice = makeFlaky(2);
attempt(flakyTwice, 5);
// "success", on the third call
const alwaysFails = makeFlaky(10);
attempt(alwaysFails, 3);
// throws MaxRetriesExceededError: Failed after 3 attempts: attempt 3 failed
Hint: makeFlaky is already written for you above, it’s a closure that remembers how many times it’s been called. You only need to write attempt and MaxRetriesExceededError. A loop with a try/catch inside it, keeping track of the most recent error, covers the whole problem.
Recap
Four problems, each one small on its own but only solvable by combining several tools from across this entire course: closures, classes, private fields, recursion, custom errors, and try/catch. If you worked through these, you’ve done exactly what this course set out to teach: not just recognizing JavaScript syntax, but reaching for the right combination of it to solve something new. Keep that EventEmitter pattern in mind, it shows up again soon as the browser’s own event system.
Next lesson: back to basics, reimplementing familiar built-in string methods yourself from scratch.