Skip to content
Chapter 14Lesson 4

Quiz - The DOM and event substrate

Quiz progress

0 / 0

You loop over a to-do list to delete every completed item, removing each one as you go:

const list = document.getElementById('list');
for (let i = 0; i < list.children.length; i++) {
if (list.children[i].classList.contains('done')) {
list.children[i].remove();
}
}

Two completed items sit next to each other and the second one survives. Why, and what is the fix?

list.children is a live collection: removing an item shifts every later element down by one index, so the loop’s i++ steps right over the item that just slid into the removed slot. Snapshot first — iterate [...list.children] — so the list you walk can’t shift under you.

classList.contains('done') is case-sensitive and misses the second item’s class. Compare against className directly instead.

.remove() is asynchronous, so the second deletion hasn’t finished when the loop ends. Await each removal before continuing.

A teammate wants to re-enable a button and writes button.setAttribute('disabled', 'false'). They also gate some logic on Boolean(button.getAttribute('disabled')). What actually happens?

The button stays disabled — for a boolean attribute, presence is the value, so the string 'false' still counts as present. And Boolean('false') is true because every non-empty string is truthy, so the gate reads “disabled” too. Use the typed property: button.disabled = false.

The button is enabled, because 'false' is coerced to the boolean false when written to a boolean attribute — but the Boolean(...) gate is still wrong and reads stale state.

The setAttribute call throws, because 'false' is invalid markup for a boolean attribute and the DOM rejects it at write time.

A delegated click listener on a <ul> routes actions for all its <li> rows. One row also has its own click handler that calls event.stopPropagation(). The team notices the delegated handler silently stops running for that row, with no error. What is going on?

Delegation depends on the event bubbling up to the ancestor. stopPropagation() halts the trip, so the event never reaches the <ul> — the delegated handler never fires, and nothing reports the failure. stopPropagation is a design smell; the row handler almost certainly wanted preventDefault instead.

stopPropagation() also cancels the browser’s default action, which suppresses the synthetic click the <ul> listens for. Add preventDefault() in the delegated handler to restore it.

The row handler ran first and consumed the event object, so by the time it reaches the <ul> the event.target is null. Clone the event before the row handler runs.

You’re attaching a wheel listener to a scrollable panel <div> (not window/document) inside a React effect, and you’ll tear it down on unmount. Which setup is the 2026 reflex?

const controller = new AbortController();
panel.addEventListener('wheel', () => updateParallax(), {
passive: true,
signal: controller.signal,
});
// cleanup: controller.abort();
const onWheel = () => updateParallax();
panel.addEventListener('wheel', onWheel);
// cleanup: panel.removeEventListener('wheel', onWheel);
panel.addEventListener('wheel', () => updateParallax());
// no cleanup needed — the browser defaults wheel listeners to passive and removes them on unmount

Quiz complete

Score by topic