Practice
JavaScript exercises
Make a change, predict what it will do, and compare the result.
- 1
JavaScript Variables
Use const and let for values.
Adapt the lesson example. Change one value or element, predict the result, and explain why it changes.
Show starting example
JavaScriptconst name = "Ada"; let score = 82; score += 9; console.log(name, score); - 2
JavaScript Functions
Write reusable logic.
Adapt the lesson example. Change one value or element, predict the result, and explain why it changes.
Show starting example
JavaScriptconst average = values => values.reduce((a, b) => a + b, 0) / values.length; console.log(average([82, 91, 76, 95])); - 3
JavaScript conditions and array loops
Filter values with strict comparisons and accumulate a total.
Change the program to count positive values rather than sum them.
Reveal answer and reasoning
Increment total by 1 for every positive value; the result is 2.
Show starting example
JavaScriptconst values = [-2, 0, 3, 5]; let total = 0; for (const value of values) { if (value > 0) total += value; } console.log(total); if (total !== 8) throw new Error("Unexpected total"); - 4
JavaScript map, filter, and reduce
Build a transformation pipeline without mutating the source.
Use [1, 3, 5] as input.
Reveal answer and reasoning
squares is [] and total is 0.
Show starting example
JavaScriptconst values = [1, 2, 3, 4]; const even = values.filter(value => value % 2 === 0); const squares = even.map(value => value * value); const total = squares.reduce((sum, value) => sum + value, 0); console.log(JSON.stringify(squares)); console.log(total); if ([].reduce((sum, value) => sum + value, 0) !== 0) throw new Error("empty"); - 5
JavaScript types, equality, and coercion
Know the primitive types, why === is safer than ==, and which values count as false.
What does [] + {} produce?
Reveal answer and reasoning
The string "[object Object]": the empty array becomes an empty string, and the object becomes "[object Object]", so + concatenates them.
Show starting example
JavaScriptconsole.log(typeof 42, typeof "text", typeof true, typeof undefined, typeof null, typeof {}, typeof []); console.log(0.1 + 0.2 === 0.3, Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON); console.log("5" + 3, "5" - 3, "5" * "2"); console.log(1 == "1", 1 === "1", null == undefined, null === undefined); console.log(Boolean(""), Boolean("0"), Boolean(0), Boolean([]), Boolean(NaN)); console.log(NaN === NaN, Number.isNaN(NaN)); - 6
JavaScript closures and scope
Use a function that remembers its surrounding variables, and see why let fixes the classic loop bug.
What does a.increment() return the first time?
Reveal answer and reasoning
1, because ++count increments first and then returns the new value.
Show starting example
JavaScriptfunction makeCounter() { let count = 0; return { increment: () => ++count, value: () => count, }; } const a = makeCounter(); const b = makeCounter(); a.increment(); a.increment(); b.increment(); console.log(a.value(), b.value()); const withVar = []; for (var i = 0; i < 3; i++) { withVar.push(() => i); } console.log(withVar.map((f) => f()).join(",")); const withLet = []; for (let j = 0; j < 3; j++) { withLet.push(() => j); } console.log(withLet.map((f) => f()).join(",")); - 7
JavaScript destructuring, spread, and rest
Unpack objects and arrays into variables, copy with spread, and collect arguments with rest.
How would you swap two variables with destructuring?
Reveal answer and reasoning
[a, b] = [b, a]; the right side builds a new array first, and the left side unpacks it.
Show starting example
JavaScriptconst user = { name: "Ada", age: 36, address: { city: "London" } }; const { name, address: { city }, role = "guest" } = user; console.log(name, city, role); const updated = { ...user, age: 37 }; console.log(user.age, updated.age, updated.address === user.address); const [first, , third, ...rest] = [1, 2, 3, 4, 5]; console.log(first, third, rest.join(",")); function total(label, ...numbers) { return label + ": " + numbers.reduce((sum, n) => sum + n, 0); } console.log(total("sum", 1, 2, 3, 4)); const merged = [...[1, 2], ...[3, 4]]; console.log(merged.join(",")); - 8
JavaScript string methods and regular expressions
Split, pad, replace, and extract parts of text with strings and regular expressions.
What does "2024-03-15".split("-").map(Number) return?
Reveal answer and reasoning
[2024, 3, 15]: the strings are converted to numbers, so "03" becomes 3.
Show starting example
JavaScriptconsole.log("a-b-c".split("-").reverse().join("+")); console.log(" padded ".trim().padStart(8, "*")); const text = "The year 2024 and 1999 were notable"; console.log(text.match(/\d{4}/g).join(" ")); const m = "2024-03-15".match(/^(\d{4})-(\d{2})-(\d{2})$/); console.log(m[1], m[2], m[3]); console.log("hello world".replace(/o/g, "0")); console.log("camelCaseString".replace(/([A-Z])/g, "_$1").toLowerCase()); console.log(/^[\w.]+@[\w.]+\.\w+$/.test("ada@example.com"), /^[\w.]+@[\w.]+\.\w+$/.test("not an email")); - 9
JavaScript promises, async, and await
Write asynchronous code that reads like synchronous code, and handle errors with try and catch.
How long does await delay("x", 20); await delay("y", 30); take compared with Promise.all?
Reveal answer and reasoning
About 50 ms when awaited one after the other, against about 30 ms with Promise.all, since the delays overlap.
Show starting example
JavaScriptfunction delay(value, ms) { return new Promise((resolve) => setTimeout(() => resolve(value), ms)); } async function main() { console.log("start"); const a = await delay("A", 20); console.log("got " + a); const [b, c] = await Promise.all([delay("B", 30), delay("C", 10)]); console.log("got " + b + c); try { await Promise.reject(new Error("boom")); } catch (error) { console.log("caught " + error.message); } const winner = await Promise.race([delay("slow", 60), delay("fast", 5)]); console.log("race winner " + winner); } main().then(() => console.log("done")); console.log("after main() call"); - 10
JavaScript classes and private fields
Protect instance state with a private class field.
Add a reset method and call it on first.
Reveal answer and reasoning
Inside the class, reset() can assign this.#value = 0. The output then becomes 0 0.
Show starting example
JavaScriptclass Counter { #value = 0; increment() { this.#value += 1; } get value() { return this.#value; } } const first = new Counter(); const second = new Counter(); first.increment(); first.increment(); console.log(first.value, second.value); if (second.value !== 0) throw new Error("shared state"); - 11
JavaScript inheritance and overriding
Reuse initialization and specialize instance behavior.
Create Cat extending Animal and override speak to append ': meow'.
Reveal answer and reasoning
A Cat('Luna') in the loop prints Luna: meow without changing the loop body.
Show starting example
JavaScriptclass Animal { constructor(name) { this.name = name; } speak() { return this.name + ": sound"; } } class Dog extends Animal { constructor(name) { super(name); } speak() { return this.name + ": woof"; } } for (const animal of [new Animal("Milo"), new Dog("Rex")]) { console.log(animal.speak()); } - 12
JavaScript prototypes and class inheritance
See that classes are built on prototype chains, and extend one constructor from another.
What does rex.hasOwnProperty("name") return?
Reveal answer and reasoning
true, because name was assigned on the object itself by the constructor.
Show starting example
JavaScriptfunction Animal(name) { this.name = name; } Animal.prototype.speak = function () { return this.name + " makes a sound"; }; const rex = new Animal("Rex"); console.log(rex.speak(), Object.getPrototypeOf(rex) === Animal.prototype); class Dog extends Animal { speak() { return super.speak() + " (woof)"; } } const fido = new Dog("Fido"); console.log(fido.speak()); console.log(fido instanceof Dog, fido instanceof Animal, Object.hasOwn(fido, "speak")); - 13
JavaScript private fields, getters, and static members
Hide state with #private fields, expose it through a getter, and keep a counter on the class itself.
How would you let the balance be read but never set directly?
Reveal answer and reasoning
Provide only a getter and no setter, and change the field through methods such as deposit that enforce the rules.
Show starting example
JavaScriptclass Account { #balance = 0; static count = 0; constructor(owner) { this.owner = owner; Account.count++; } deposit(amount) { if (amount <= 0) { throw new RangeError("amount must be positive"); } this.#balance += amount; return this; } get balance() { return this.#balance; } } const account = new Account("Ada"); account.deposit(50).deposit(25); console.log(account.balance, Account.count); try { account.deposit(-1); } catch (error) { console.log(error.name + ": " + error.message); } console.log(Object.keys(account).join(",")); console.log(JSON.stringify(account)); - 14
JavaScript binary search
Search a sorted numeric array with a shrinking interval.
Search for 10 in the first array.
Reveal answer and reasoning
The algorithm inspects indices 2, 3, and 4, returning 4.
Show starting example
JavaScriptfunction binarySearch(values, target) { let low = 0, high = values.length - 1; while (low <= high) { const mid = low + Math.floor((high - low) / 2); if (values[mid] === target) return mid; if (values[mid] < target) low = mid + 1; else high = mid - 1; } return -1; } console.log(binarySearch([2, 4, 6, 8, 10], 8)); console.log(binarySearch([], 8)); if (binarySearch([2, 4], 3) !== -1) throw new Error("missing"); - 15
JavaScript Map and Set
Count values and identify distinct entries.
Add green at the end.
Reveal answer and reasoning
The serialized entries append ["green",1], and the distinct count becomes 3.
Show starting example
JavaScriptconst words = ["red", "blue", "red"]; const counts = new Map(); for (const word of words) { counts.set(word, (counts.get(word) ?? 0) + 1); } console.log(JSON.stringify([...counts])); console.log(new Set(words).size); if (counts.has("green")) throw new Error("missing key"); - 16
JavaScript linked lists and iterators
Build a singly linked list class that can be looped over with for...of.
How would you find the middle node in one pass?
Reveal answer and reasoning
Walk with two pointers: a slow one moving one node at a time and a fast one moving two. When the fast one reaches the end, the slow one is at the middle.
Show starting example
JavaScriptclass Node { constructor(value, next = null) { this.value = value; this.next = next; } } class LinkedList { constructor() { this.head = null; this.length = 0; } addFirst(value) { this.head = new Node(value, this.head); this.length++; } addLast(value) { const node = new Node(value); if (this.head === null) { this.head = node; } else { let current = this.head; while (current.next !== null) { current = current.next; } current.next = node; } this.length++; } reverse() { let previous = null; let current = this.head; while (current !== null) { const next = current.next; current.next = previous; previous = current; current = next; } this.head = previous; } *[Symbol.iterator]() { for (let node = this.head; node !== null; node = node.next) { yield node.value; } } } const list = new LinkedList(); list.addFirst(2); list.addFirst(1); list.addLast(3); list.addLast(4); console.log([...list].join(" -> "), "(" + list.length + " nodes)"); list.reverse(); console.log([...list].join(" -> ")); let sum = 0; for (const value of list) { sum += value; } console.log("sum " + sum); - 17
JavaScript stacks and queues
Use an array as a stack, and write a queue that does not shift the whole array on every removal.
Which structure would you use for the browser's back button?
Reveal answer and reasoning
A stack: the most recently visited page is the first one you go back to.
Show starting example
JavaScriptclass Queue { constructor() { this.items = []; this.head = 0; } enqueue(value) { this.items.push(value); } dequeue() { if (this.head === this.items.length) { return undefined; } const value = this.items[this.head]; this.head++; return value; } get size() { return this.items.length - this.head; } } const stack = []; stack.push("a"); stack.push("b"); stack.push("c"); console.log(stack.pop(), stack.pop(), stack.length); const queue = new Queue(); queue.enqueue(1); queue.enqueue(2); queue.enqueue(3); console.log(queue.dequeue(), queue.dequeue(), queue.size); console.log(queue.dequeue(), queue.dequeue()); - 18
JavaScript sorting and the default sort pitfall
Learn why [10, 9, 1].sort() is wrong, sort with comparators, and write a merge sort.
How do you sort an array of numbers in descending order?
Reveal answer and reasoning
arr.sort((a, b) => b - a): swapping the operands reverses the order.
Show starting example
JavaScriptconsole.log([10, 9, 1].sort().join(",")); console.log([10, 9, 1].sort((a, b) => a - b).join(",")); function mergeSort(items) { if (items.length <= 1) { return items; } const middle = Math.floor(items.length / 2); const left = mergeSort(items.slice(0, middle)); const right = mergeSort(items.slice(middle)); const merged = []; let i = 0; let j = 0; while (i < left.length && j < right.length) { merged.push(left[i] <= right[j] ? left[i++] : right[j++]); } return merged.concat(left.slice(i), right.slice(j)); } console.log(mergeSort([5, 2, 9, 1, 5, 6]).join(",")); const people = [ { name: "Ada", age: 30 }, { name: "Bo", age: 25 }, { name: "Cy", age: 30 }, { name: "Di", age: 25 }, ]; people.sort((p, q) => p.age - q.age); console.log(people.map((p) => p.name).join(",")); console.log(["pear", "Apple", "fig"].sort((a, b) => a.localeCompare(b)).join(",")); - 19
JavaScript binary search trees with a generator
Insert values, search, and produce the sorted order lazily with a recursive generator.
Why use yield* instead of a loop with yield?
Reveal answer and reasoning
yield* delegates to another iterable, here the recursive generator for a subtree, and passes along all its values without a manual loop.
Show starting example
JavaScriptclass TreeNode { constructor(value) { this.value = value; this.left = null; this.right = null; } } class Bst { constructor() { this.root = null; } insert(value) { const node = new TreeNode(value); if (this.root === null) { this.root = node; return; } let current = this.root; while (true) { if (value === current.value) { return; } const side = value < current.value ? "left" : "right"; if (current[side] === null) { current[side] = node; return; } current = current[side]; } } has(value) { let current = this.root; while (current !== null) { if (value === current.value) { return true; } current = value < current.value ? current.left : current.right; } return false; } *inOrder(node = this.root) { if (node === null) { return; } yield* this.inOrder(node.left); yield node.value; yield* this.inOrder(node.right); } height(node = this.root) { return node === null ? 0 : 1 + Math.max(this.height(node.left), this.height(node.right)); } } const tree = new Bst(); for (const value of [50, 30, 70, 20, 40, 60, 80, 40]) { tree.insert(value); } console.log([...tree.inOrder()].join(" ")); console.log(tree.has(60), tree.has(65)); console.log("height " + tree.height()); const firstThree = []; for (const value of tree.inOrder()) { firstThree.push(value); if (firstThree.length === 3) { break; } } console.log(firstThree.join(" ")); - 20
JavaScript graphs: BFS, DFS, and path existence
Store a graph as a Map of neighbour lists and search it breadth-first and depth-first.
How would you get the shortest path between two nodes?
Reveal answer and reasoning
Run BFS and record each node's parent when it is first reached, then follow the parents back from the goal to the start.
Show starting example
JavaScriptconst graph = new Map([ ["A", ["B", "C"]], ["B", ["D"]], ["C", ["D", "E"]], ["D", ["F"]], ["E", ["F"]], ["F", []], ["X", ["Y"]], ["Y", []], ]); function bfs(start) { const order = []; const seen = new Set([start]); const queue = [start]; while (queue.length > 0) { const node = queue.shift(); order.push(node); for (const next of graph.get(node)) { if (!seen.has(next)) { seen.add(next); queue.push(next); } } } return order; } function dfs(node, seen = new Set(), order = []) { seen.add(node); order.push(node); for (const next of graph.get(node)) { if (!seen.has(next)) { dfs(next, seen, order); } } return order; } console.log("bfs: " + bfs("A").join(" ")); console.log("dfs: " + dfs("A").join(" ")); console.log("A reaches F: " + dfs("A").includes("F")); console.log("A reaches Y: " + dfs("A").includes("Y")); - 21
JavaScript: memoization trades memory for time
Count the calls made by a naive recursive Fibonacci and a memoized one.
How many calls does fibMemo(10) make?
Reveal answer and reasoning
19 calls: about 2n − 1, since each value is computed once and looked up once.
Show starting example
JavaScriptlet naiveCalls = 0; function fibNaive(n) { naiveCalls++; return n < 2 ? n : fibNaive(n - 1) + fibNaive(n - 2); } let memoCalls = 0; const cache = new Map(); function fibMemo(n) { memoCalls++; if (n < 2) return n; if (cache.has(n)) return cache.get(n); const value = fibMemo(n - 1) + fibMemo(n - 2); cache.set(n, value); return value; } console.log("fib(25) naive: " + fibNaive(25) + " in " + naiveCalls + " calls"); console.log("fib(25) memo: " + fibMemo(25) + " in " + memoCalls + " calls"); - 22
JavaScript: counting the steps of binary search
Show that searching a sorted array of 1000 items takes at most about ten comparisons.
About how many steps does a sorted array of one million items need?
Reveal answer and reasoning
At most 20 steps, because log2(1,000,000) is about 19.9.
Show starting example
JavaScriptfunction binarySearch(sorted, target) { let low = 0; let high = sorted.length - 1; let steps = 0; while (low <= high) { steps++; const mid = Math.floor((low + high) / 2); if (sorted[mid] === target) return { index: mid, steps: steps }; if (sorted[mid] < target) low = mid + 1; else high = mid - 1; } return { index: -1, steps: steps }; } const data = Array.from({ length: 1000 }, (_, i) => i * 2); const found = binarySearch(data, 1500); const missing = binarySearch(data, 999); console.log("1500: index " + found.index + " in " + found.steps + " steps"); console.log("999: index " + missing.index + " in " + missing.steps + " steps"); console.log("bound: " + Math.ceil(Math.log2(data.length + 1))); - 23
JavaScript: an index turns a scan into a lookup
Compare scanning an array for a row with looking it up in a Map keyed by id.
What happens to the index when a user is deleted?
Reveal answer and reasoning
The entry must also be removed from the Map, with byId.delete(id), or lookups would still find the deleted user.
Show starting example
JavaScriptconst users = Array.from({ length: 1000 }, (_, i) => ({ id: i + 1, name: "user" + (i + 1) })); let scanSteps = 0; function findByScan(id) { for (const user of users) { scanSteps++; if (user.id === id) return user; } return null; } const byId = new Map(users.map((user) => [user.id, user])); console.log(findByScan(900).name + " after " + scanSteps + " rows scanned"); console.log(byId.get(900).name + " after 1 index lookup"); console.log("missing id: " + byId.get(5000) + ", scan finds " + findByScan(5000)); - 24
JavaScript: GROUP BY and HAVING with Map
Total a column per group, keep the groups that pass a condition, and sort them.
How would you also count the orders per customer?
Reveal answer and reasoning
Store an object such as { sum, count } in the Map and increment both fields for each order.
Show starting example
JavaScriptconst orders = [ { customer: "Ada", amount: 30 }, { customer: "Bo", amount: 20 }, { customer: "Ada", amount: 50 }, { customer: "Cy", amount: 5 }, { customer: "Bo", amount: 25 }, ]; const totals = new Map(); for (const order of orders) { totals.set(order.customer, (totals.get(order.customer) ?? 0) + order.amount); } const result = [...totals] .filter(([, sum]) => sum >= 30) .sort((a, b) => b[1] - a[1]); for (const [customer, sum] of result) { console.log(customer + " " + sum); } - 25
JavaScript: the event loop as a cooperative scheduler
Predict the order in which synchronous code, nextTick, promises, and timers run in Node.js.
Where would a setImmediate callback run in this order?
Reveal answer and reasoning
After the microtasks, in the check phase of the event loop. Relative to a 0 ms timer the order can vary when called from the main module.
Show starting example
JavaScriptconsole.log("script start"); setTimeout(() => console.log("timeout (macrotask)"), 0); Promise.resolve().then(() => console.log("promise (microtask)")); process.nextTick(() => console.log("nextTick")); console.log("script end"); - 26
JavaScript: LRU replacement with a Map
Use Map's insertion order to evict the least recently used page and count page faults.
How many faults would FIFO with 3 frames have on this string?
Reveal answer and reasoning
15 faults, so LRU is better here.
Show starting example
JavaScriptclass LruMemory { constructor(frames) { this.frames = frames; this.pages = new Map(); this.faults = 0; } access(page) { if (this.pages.has(page)) { this.pages.delete(page); } else { this.faults++; if (this.pages.size === this.frames) { const oldest = this.pages.keys().next().value; this.pages.delete(oldest); } } this.pages.set(page, true); } } const refs = [7, 0, 1, 2, 0, 3, 0, 4, 2, 3, 0, 3, 2, 1, 2, 0, 1, 7, 0, 1]; const memory = new LruMemory(3); for (const page of refs) { memory.access(page); } console.log("faults: " + memory.faults); console.log("resident, oldest first: " + [...memory.pages.keys()].join(" ")); - 27
JavaScript: an HTTP server and client on the loopback interface
Start a Node.js HTTP server on a free port and request it with fetch to see status codes.
Which status code would signal a server error?
Reveal answer and reasoning
500 Internal Server Error, or another code in the 5xx range.
Show starting example
JavaScriptconst http = require("node:http"); const server = http.createServer((req, res) => { const known = req.url === "/hello"; res.statusCode = known ? 200 : 404; res.setHeader("Content-Type", "text/plain"); res.end(known ? "Hello" : "Not found"); }); server.listen(0, "127.0.0.1", async () => { const base = "http://127.0.0.1:" + server.address().port; for (const path of ["/hello", "/missing"]) { const response = await fetch(base + path); console.log(path + " -> " + response.status + " " + (await response.text())); } server.close(); server.closeAllConnections(); }); - 28
JavaScript: a TCP echo server with the net module
Send a line through a TCP socket and print the server's reply.
How would you let the server handle many clients?
Reveal answer and reasoning
Nothing extra is needed: createServer calls the handler for every connection, and each socket has its own events.
Show starting example
JavaScriptconst net = require("node:net"); const server = net.createServer((socket) => { socket.on("data", (chunk) => { socket.end("echo: " + chunk.toString().trim() + "\n"); }); }); server.listen(0, "127.0.0.1", () => { const client = net.connect(server.address().port, "127.0.0.1", () => { client.write("hello\n"); }); client.on("data", (reply) => { console.log(reply.toString().trim()); }); client.on("close", () => server.close()); }); - 29
JavaScript: unit tests with node:assert
Write small tests with assertions, including checks that bad input throws the right error.
Add a test for a negative number.
Reveal answer and reasoning
test("rejects -1", () => assert.throws(() => percent("-1"), RangeError)); -1 is below the allowed minimum of 0.
Show starting example
JavaScriptconst assert = require("node:assert/strict"); function percent(text) { const n = Number(text); if (text.trim() === "" || !Number.isInteger(n) || n < 0 || n > 100) { throw new RangeError("invalid percent: " + text); } return n; } let failures = 0; function test(name, fn) { try { fn(); console.log("PASS " + name); } catch (error) { failures++; console.log("FAIL " + name + ": " + error.message); } } test("parses 42", () => assert.equal(percent("42"), 42)); test("accepts the boundary 100", () => assert.equal(percent("100"), 100)); test("rejects 101", () => assert.throws(() => percent("101"), RangeError)); test("rejects an empty string", () => assert.throws(() => percent(""))); test("reports the bad text", () => assert.throws(() => percent("abc"), { message: "invalid percent: abc" })); console.log("failures: " + failures); - 30
JavaScript: the observer pattern with EventEmitter
Let several listeners react to the same event without the emitter knowing about them.
How do you remove a listener?
Reveal answer and reasoning
Keep a reference to the function and call bus.off('order', fn), or bus.removeListener('order', fn).
Show starting example
JavaScriptconst { EventEmitter } = require("node:events"); const bus = new EventEmitter(); const log = []; bus.on("order", (id) => log.push("logged " + id)); bus.on("order", (id) => console.log("notify customer for order " + id)); bus.once("order", (id) => console.log("first order bonus for " + id)); bus.emit("order", 1); bus.emit("order", 2); console.log(log.join(", ")); - 31
JavaScript: SHA-256 hashing and HMAC with node:crypto
Hash a message, authenticate it with a secret key, and compare tags in constant time.
Does an HMAC hide the message?
Reveal answer and reasoning
No. It proves who wrote the message and that it was not changed, but the message itself is still readable.
Show starting example
JavaScriptconst crypto = require("node:crypto"); const sha = (text) => crypto.createHash("sha256").update(text).digest("hex"); console.log(sha("abc")); console.log(sha("abd")); const message = "what do ya want for nothing?"; const tag = crypto.createHmac("sha256", "Jefe").update(message).digest(); console.log(tag.toString("hex")); const again = crypto.createHmac("sha256", "Jefe").update(message).digest(); const forged = crypto.createHmac("sha256", "Jefe").update("what do ya want for something?").digest(); console.log("same message valid: " + crypto.timingSafeEqual(tag, again)); console.log("tampered message valid: " + crypto.timingSafeEqual(tag, forged)); - 32
JavaScript: storing passwords with scrypt and a salt
Derive a memory-hard hash from a password and a random salt, then verify a login attempt.
Why is a unique salt per user important?
Reveal answer and reasoning
With a shared salt, identical passwords would give identical hashes, and one guess could crack many accounts at once.
Show starting example
JavaScriptconst crypto = require("node:crypto"); const salt = crypto.randomBytes(16); const stored = crypto.scryptSync("correct horse", salt, 32); const good = crypto.scryptSync("correct horse", salt, 32); const bad = crypto.scryptSync("wrong guess", salt, 32); console.log("hash length: " + stored.length + " bytes"); console.log("right password: " + crypto.timingSafeEqual(stored, good)); console.log("wrong password: " + crypto.timingSafeEqual(stored, bad)); - 33
JavaScript: preventing XSS by escaping output
Turn special HTML characters into entities so user text is shown as text, never run as markup.
Why is the ampersand escaped as well?
Reveal answer and reasoning
An unescaped & could begin an entity written by an attacker. Escaping it ensures that the browser shows the characters literally.
Show starting example
JavaScriptconst entities = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }; function escapeHtml(text) { return text.replace(/[&<>"']/g, (ch) => entities[ch]); } const comment = '<img src=x onerror="alert(1)">'; console.log("<p>" + comment + "</p>"); console.log("<p>" + escapeHtml(comment) + "</p>"); console.log("<p>" + escapeHtml("Tom & Jerry") + "</p>"); - 34
JavaScript: concurrency without parallelism
See how async functions interleave on a single thread and finish together under Promise.all.
What would the log look like with only workers A (3 steps) and C (3 steps)?
Reveal answer and reasoning
A1 C1 A2 C2 A3 C3, because they alternate turn by turn.
Show starting example
JavaScriptconst log = []; async function worker(name, steps) { for (let i = 1; i <= steps; i++) { log.push(name + i); await null; } return name; } Promise.all([worker("A", 3), worker("B", 2), worker("C", 3)]).then((names) => { console.log(log.join(" ")); console.log(names.join(",")); }); - 35
JavaScript: real parallelism with worker threads
Split an array between two worker threads and add their partial results.
How would you use four workers?
Reveal answer and reasoning
Split the array into four slices, start four workers, and wait for the four messages with Promise.all.
Show starting example
JavaScriptconst { Worker, isMainThread, parentPort, workerData } = require("node:worker_threads"); if (isMainThread) { const data = Array.from({ length: 1000 }, (_, i) => i + 1); const halves = [data.slice(0, 500), data.slice(500)]; const jobs = halves.map((part) => new Promise((resolve, reject) => { const worker = new Worker(__filename, { workerData: part }); worker.on("message", resolve); worker.on("error", reject); })); Promise.all(jobs).then((sums) => { console.log(sums.join(" ") + " total " + sums.reduce((a, b) => a + b, 0)); }); } else { parentPort.postMessage(workerData.reduce((a, b) => a + b, 0)); } - 36
JavaScript: breadth-first search for the fewest steps
Find a shortest path in an unweighted graph by exploring it layer by layer.
Are there other shortest paths from A to F?
Reveal answer and reasoning
Yes: A → C → D → F and A → C → E → F, both also with three edges. BFS returns the first one it finds.
Show starting example
JavaScriptconst graph = { A: ["B", "C"], B: ["D"], C: ["D", "E"], D: ["F"], E: ["F"], F: [], }; function shortestPath(start, goal) { const queue = [[start]]; const seen = new Set([start]); while (queue.length > 0) { const path = queue.shift(); const node = path[path.length - 1]; if (node === goal) return path; for (const next of graph[node]) { if (!seen.has(next)) { seen.add(next); queue.push([...path, next]); } } } return null; } console.log(shortestPath("A", "F").join(" -> ")); console.log(shortestPath("F", "A")); - 37
JavaScript: constraint satisfaction by backtracking
Color a map so that neighbouring regions differ, counting every valid coloring.
How many solutions are there with only 2 colors?
Reveal answer and reasoning
None, because the three mutually neighbouring regions WA, NT, and SA need three different colors.
Show starting example
JavaScriptconst regions = ["WA", "NT", "SA", "Q", "NSW", "V", "T"]; const borders = [ ["WA", "NT"], ["WA", "SA"], ["NT", "SA"], ["NT", "Q"], ["SA", "Q"], ["SA", "NSW"], ["SA", "V"], ["Q", "NSW"], ["NSW", "V"], ]; const colors = ["red", "green", "blue"]; function ok(assignment, region, color) { return borders.every(([a, b]) => { if (a === region && assignment[b] === color) return false; if (b === region && assignment[a] === color) return false; return true; }); } let count = 0; let first = null; function solve(index, assignment) { if (index === regions.length) { count++; if (first === null) first = { ...assignment }; return; } const region = regions[index]; for (const color of colors) { if (ok(assignment, region, color)) { assignment[region] = color; solve(index + 1, assignment); delete assignment[region]; } } } solve(0, {}); console.log("solutions: " + count); console.log(regions.map((r) => r + "=" + first[r]).join(" ")); - 38
JavaScript: querying facts stored as triples
Represent knowledge as subject–predicate–object triples and answer queries with variables.
How do you find all of Byron's children?
Reveal answer and reasoning
Query [["byron", "parentOf", "?child"]]; it binds ?child to cy.
Show starting example
JavaScriptconst facts = [ ["ada", "parentOf", "byron"], ["byron", "parentOf", "cy"], ["cy", "parentOf", "di"], ["ada", "type", "person"], ]; function matchPattern(pattern, fact, bindings) { const result = { ...bindings }; for (let i = 0; i < 3; i++) { const term = pattern[i]; if (term.startsWith("?")) { if (term in result && result[term] !== fact[i]) return null; result[term] = fact[i]; } else if (term !== fact[i]) { return null; } } return result; } function query(patterns) { let answers = [{}]; for (const pattern of patterns) { const next = []; for (const bindings of answers) { for (const fact of facts) { const extended = matchPattern(pattern, fact, bindings); if (extended) next.push(extended); } } answers = next; } return answers; } const grandparents = query([["?x", "parentOf", "?y"], ["?y", "parentOf", "?z"]]); for (const a of grandparents) { console.log(a["?x"] + " is a grandparent of " + a["?z"]); } console.log(query([["?who", "type", "person"]]).length + " person"); - 39
JavaScript: proof by resolution refutation
Show that a knowledge base entails a query by deriving a contradiction from the knowledge base plus the negated query.
What happens if the query is A instead of C?
Reveal answer and reasoning
The empty clause is never derived, because the knowledge base has a model where A is false (B and C true), so A is not entailed.
Show starting example
JavaScriptconst show = (clause) => (clause.size === 0 ? "empty" : [...clause].sort().join(" v ")); function resolvents(c1, c2) { const out = []; for (const literal of c1) { const negation = literal.startsWith("~") ? literal.slice(1) : "~" + literal; if (c2.has(negation)) { const merged = new Set([...c1, ...c2]); merged.delete(literal); merged.delete(negation); out.push(merged); } } return out; } const clauses = [ new Set(["A", "B"]), new Set(["~A", "C"]), new Set(["~B", "C"]), new Set(["~C"]), ]; const known = new Set(clauses.map(show)); let proved = false; for (let i = 0; i < clauses.length && !proved; i++) { for (let j = 0; j < i && !proved; j++) { for (const r of resolvents(clauses[i], clauses[j])) { if (known.has(show(r))) continue; known.add(show(r)); clauses.push(r); console.log("derived " + show(r)); if (r.size === 0) proved = true; } } } console.log("KB entails C: " + proved); - 40
JavaScript: fitting a line with least squares
Compute the slope, intercept, and R² of the best-fit line in closed form.
What does the model predict for x = 0?
Reveal answer and reasoning
The intercept, which is 0.22.
Show starting example
JavaScriptconst x = [1, 2, 3, 4, 5]; const y = [2.2, 4.1, 6.2, 7.9, 10.1]; const mean = (values) => values.reduce((a, b) => a + b, 0) / values.length; const mx = mean(x); const my = mean(y); let sxy = 0; let sxx = 0; for (let i = 0; i < x.length; i++) { sxy += (x[i] - mx) * (y[i] - my); sxx += (x[i] - mx) ** 2; } const slope = sxy / sxx; const intercept = my - slope * mx; let residual = 0; let total = 0; for (let i = 0; i < x.length; i++) { residual += (y[i] - (slope * x[i] + intercept)) ** 2; total += (y[i] - my) ** 2; } console.log("slope " + slope.toFixed(3) + ", intercept " + intercept.toFixed(3)); console.log("R squared " + (1 - residual / total).toFixed(4)); console.log("prediction for x=6: " + (slope * 6 + intercept).toFixed(2)); - 41
JavaScript: a naive Bayes text classifier
Classify short messages as spam or ham from word counts with Laplace smoothing.
What would happen to the prior if the training set had three spam messages and one ham?
Reveal answer and reasoning
The spam prior would be 3/4 and the ham prior 1/4, so spam would be favoured when the words are equally likely in both classes.
Show starting example
JavaScriptconst training = [ ["spam", "win money now"], ["spam", "win a prize now"], ["ham", "meeting at noon"], ["ham", "lunch at noon now"], ]; const counts = { spam: new Map(), ham: new Map() }; const docs = { spam: 0, ham: 0 }; const vocabulary = new Set(); for (const [label, text] of training) { docs[label]++; for (const word of text.split(" ")) { vocabulary.add(word); counts[label].set(word, (counts[label].get(word) ?? 0) + 1); } } function score(label, text) { const total = [...counts[label].values()].reduce((a, b) => a + b, 0); let logProbability = Math.log(docs[label] / training.length); for (const word of text.split(" ")) { const seen = counts[label].get(word) ?? 0; logProbability += Math.log((seen + 1) / (total + vocabulary.size)); } return logProbability; } for (const message of ["win prize now", "lunch meeting"]) { const spam = score("spam", message); const ham = score("ham", message); console.log(message + " -> " + (spam > ham ? "spam" : "ham") + " (spam " + spam.toFixed(2) + ", ham " + ham.toFixed(2) + ")"); } - 42
JavaScript: a forward pass through two dense layers
Multiply by weight matrices, add biases, apply ReLU, and finish with softmax probabilities.
Which output class has the highest probability?
Reveal answer and reasoning
The class whose score is largest. Softmax preserves the ranking of the raw scores.
Show starting example
JavaScriptconst relu = (z) => Math.max(0, z); function dense(input, weights, biases, activation) { return weights.map((row, j) => activation(row.reduce((sum, w, i) => sum + w * input[i], biases[j]))); } function softmax(scores) { const largest = Math.max(...scores); const exps = scores.map((s) => Math.exp(s - largest)); const total = exps.reduce((a, b) => a + b, 0); return exps.map((e) => e / total); } const input = [1, 2]; const w1 = [[0.5, -0.2], [0.3, 0.8], [-0.6, 0.1]]; const b1 = [0.1, 0.0, 0.2]; const w2 = [[1.0, -1.0, 0.5], [0.2, 0.4, -0.3], [-0.5, 0.9, 0.1]]; const b2 = [0.0, 0.1, -0.1]; const hidden = dense(input, w1, b1, relu); const scores = dense(hidden, w2, b2, (z) => z); const probabilities = softmax(scores); console.log("hidden: " + hidden.map((h) => h.toFixed(2)).join(" ")); console.log("scores: " + scores.map((s) => s.toFixed(3)).join(" ")); console.log("probabilities: " + probabilities.map((p) => p.toFixed(3)).join(" ")); console.log("sum: " + probabilities.reduce((a, b) => a + b, 0).toFixed(3)); - 43
JavaScript: checking a gradient numerically
Compare a hand-derived gradient with a finite-difference estimate, the standard test for backpropagation code.
What would a large difference between the two values mean?
Reveal answer and reasoning
There is probably a bug in the analytic derivative or in the loss, so backpropagation would train badly.
Show starting example
JavaScriptconst sigmoid = (z) => 1 / (1 + Math.exp(-z)); const x = 1.5; const target = 1; const b = -0.3; function loss(w) { const a = sigmoid(w * x + b); return (a - target) ** 2; } function analyticGradient(w) { const a = sigmoid(w * x + b); return 2 * (a - target) * a * (1 - a) * x; } const w = 0.4; const h = 1e-5; const numeric = (loss(w + h) - loss(w - h)) / (2 * h); const analytic = analyticGradient(w); console.log("analytic: " + analytic.toFixed(8)); console.log("numeric: " + numeric.toFixed(8)); console.log("close enough: " + (Math.abs(analytic - numeric) < 1e-8)); - 44
JavaScript: RGBA pixel arrays and Uint8ClampedArray
Convert pixels stored the way a canvas stores them to grayscale and see how clamping works.
At which index does the third pixel start?
Reveal answer and reasoning
At index 8, because pixel number 2 (counting from 0) starts at 4 × 2.
Show starting example
JavaScriptconst pixels = new Uint8ClampedArray([ 255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255, 255, 255, 255, 255, ]); for (let i = 0; i < pixels.length; i += 4) { const gray = Math.round(0.299 * pixels[i] + 0.587 * pixels[i + 1] + 0.114 * pixels[i + 2]); pixels[i] = gray; pixels[i + 1] = gray; pixels[i + 2] = gray; } console.log(pixels.join(",")); const clamped = new Uint8ClampedArray([300, -20, 127.6]); console.log(clamped.join(",")); - 45
JavaScript: a sharpen filter and why clamping matters
Apply a sharpening kernel to a step edge and see the overshoot clamped to the valid range.
What would the raw value be at a flat pixel where the pixel and all four neighbours equal 100?
Reveal answer and reasoning
5·100 − 4·100 = 100, so flat regions are unchanged.
Show starting example
JavaScriptconst width = 5; const image = []; for (let r = 0; r < 5; r++) { for (let c = 0; c < width; c++) { image.push(c < 2 ? 50 : 200); } } const clamp = (v) => Math.min(255, Math.max(0, v)); const at = (r, c) => image[r * width + c]; for (let r = 1; r < 4; r++) { const row = []; for (let c = 1; c < 4; c++) { const raw = 5 * at(r, c) - at(r - 1, c) - at(r + 1, c) - at(r, c - 1) - at(r, c + 1); row.push(clamp(raw)); } console.log(row.join(" ")); }