A cleanup button removes the first, third and fifth cards, leaving the second and fourth behind. The loop increased its index normally, and every remove call reached the element it was given. Why did the cleanup skip cards?
The short answer is that a live collection changes as its underlying document changes. Removing an element can shift the next matching element into the index just visited. Increasing the index then advances past that element. A static selection preserves its selected membership, while its element references still point to objects that can change or leave the document.
This guide follows an original fictional reading list through removal, class changes, insertion and reordering. The ledgers distinguish element identity from collection position. They are paper traces based on the DOM collection contract, not browser-execution results or a recommendation to remove a visitor's work without an appropriate product decision.
This specialist guide belongs to the Websites root. It also connects to The Gold Standard of Education | Learning That Transfers into Life and the How X Works hub. The worked cases are original teaching examples; their numbers are stipulated so that the reasoning can be checked.
eduKateSG · Websites
Find your next learning step
Follow A through E through a changing collection. Choose a route to see why indices shift, what a static selection preserves, and which traversal matches the intended action.
ROUTE 1 · CHAPTERS 1–4
Trace the first skipped card
Separate identities, indices, root and filter before changing code.
ROUTE 2 · CHAPTERS 5–7
Compare removal strategies
Test fixed lengths, reverse traversal and current-member draining.
ROUTE 3 · CHAPTERS 8–10
Choose the work set
Compare static selections, copied references and class-filter changes.
ROUTE 4 · CHAPTERS 11–14
Change the matching set
Predict hiding, insertion, reordering and eligibility.
ROUTE 5 · CHAPTERS 15–18
Verify the traversal contract
Audit helper effects, termination, reproductions and explanation.
Chapter contents
Trace the first skipped card · Chapters 1–4
1. Define which cards the operation owns
2. Understand the collection’s root and filter
Compare removal strategies · Chapters 5–7
5. Explain why caching only the initial length is insufficient
Choose the work set · Chapters 8–10
8. Freeze membership with a static selection
Change the matching set · Chapters 11–14
11. Separate visibility from matching membership
12. Insert a member while traversal is underway
13. Reorder members without changing the count
14. Keep static membership separate from current eligibility
Verify the traversal contract · Chapters 15–18
15. Treat callbacks as possible changes to the traversal contract
16. Choose a strategy from the intended set
CHAPTER 1 OF 18 · Trace the first skipped card
1. Define which cards the operation owns
Imagine a reading interface with five temporary practice cards. Each is labelled with an identity: A, B, C, D and E. The task is to clear these temporary cards when the learner starts a new practice set. Saved notes elsewhere on the page are outside the operation.
Before choosing a loop, establish the intended set. Does clear mean the cards selected at the moment the learner pressed the button? Does it mean every matching card currently in the container, including cards added during cleanup? Or does it mean only completed cards? Those are different operations.
For the initial exercise, all five cards are direct element children of one dedicated container. There are no headings, helper elements or nested cards inside that container. No callback adds, removes or reorders another card while the loop runs. These restrictions let the index change stand on its own.
The initial order is A, B, C, D, E. The intended outcome is that all five original cards leave the container. We are not yet deciding how the application handles keyboard focus, undo, announcements or persistence. Those remain part of designing a real cleanup action.
Write down the intended identities before looking at indices. The labels A through E continue to identify the same objects even after their positions change. If a debugging record says only index two was removed, it hides the very movement needed to explain the failure.
CHAPTER 2 OF 18 · Trace the first skipped card
2. Understand the collection’s root and filter
A DOM collection represents nodes associated with a root and a filter. Its membership and order follow the relevant API contract. A live collection operates on the underlying data as that data changes; a static collection keeps the selection result obtained when it was created.
For the simple container, its children property provides an HTMLCollection of child elements. Because the exercise contains only the five card elements, that collection begins as A, B, C, D, E. Whitespace text nodes do not become members of children.
Do not transfer that exact membership to childNodes. That property concerns child nodes and can include text nodes. Pretty-printing markup with line breaks can therefore matter to a childNodes traversal even though the visible cards look unchanged. Identify the method or property that produced a collection before interpreting its length.
A class-filtered collection has another boundary. getElementsByClassName selects matching descendants under its root. Nested matches can therefore appear even when a designer was thinking only about direct child cards. For now, keep the flat dedicated container so that the removal trace has one clear mechanism.
The practical question is not whether a variable looks like a list. It is what view of the tree the API promises. Write root, filter, liveness and order beside the first ledger. Those four fields explain why a later membership change is ordinary behaviour rather than a mysterious failure of the loop counter.
CHAPTER 3 OF 18 · Trace the first skipped card
3. Trace the forward-removal loop precisely
Consider the familiar pattern: start i at zero, continue while i is less than cards.length, remove cards[i], then increase i by one. Here cards is the live children collection from the dedicated container.
Initially i is zero and the collection is A, B, C, D, E. cards[0] is A, so the first body removes A. The collection now represents B, C, D, E. B has moved into position zero without changing its identity.
Next i becomes one. The current length is four, so the condition allows another iteration. cards[1] is now C, not B. The second body removes C. The collection becomes B, D, E. The index has progressed, but the remaining members have also shifted left.
Next i becomes two. The current length is three, so the third body runs. cards[2] is E. Removing E leaves B, D. Then i becomes three. The condition three less than two is false, so the loop stops.
Repeat the trace with four initial cards instead of five. Removing A leaves B, C, D; increasing to index one selects C; removing C leaves B, D. Increasing to two then fails the condition because the current length is two. The omission is therefore not caused by having an odd number of cards. The changing view explains both initial counts without changing the loop rule.
The removed sequence is A, C, E. The surviving sequence is B, D. Every individual indexed access and removal was consistent with the collection at that moment. The failure came from combining a growing index with a shrinking, reindexed view. That explanation is more useful than saying the browser skipped every other card, because it identifies the operation that moved the next intended card behind the index.
CHAPTER 4 OF 18 · Trace the first skipped card
4. Keep a position ledger beside the identity ledger
An index is a position in a particular collection state. It is not an enduring name for an element. The phrase card two becomes ambiguous if the code uses zero-based indices while a person is referring to the second visible card.
Use two records. The identity record says B is the same card before and after A is removed. The position record says B changes from index one to index zero. Neither statement cancels the other; together they explain the movement.
For a check, ask what occupies index one after the first removal. The answer is C. Then ask where B went. It is still in the container, at index zero. A learner who answers B was removed because index one was processed has attached the old index to the object after the collection changed.
This distinction also improves logs. Record the element identity actually obtained before calling remove, the current index and the current collection length. After the operation, record the remaining identities. A count alone cannot show which cards were missed.
Do not use the card's visible text as an infallible identity in a real application. Labels can repeat or change. The A–E markers are unique fictional identifiers for this exercise. Actual diagnostics should use an appropriate component identifier or retained object reference, with the normal care for user information in logs.
CHAPTER 5 OF 18 · Compare removal strategies
5. Explain why caching only the initial length is insufficient
A common repair stores the starting length, five, and uses that fixed number as the loop's upper bound. This changes the stopping condition but leaves indexed access attached to the live collection.
With the same forward removals, the first three accesses still select A, C and E. After those removals the collection is B, D. Increasing i to three does not reveal the original fourth card. There is no current element at index three.
How the next statement behaves depends on the access and guards used. Accessing an unsupported numeric property does not give a usable element; calling a method on that result can fail. Using item with an out-of-range index gives null under its contract. A guard might avoid an exception while still leaving B and D unprocessed.
The important conclusion is narrower than every fixed-length loop throws. A fixed length does not freeze the indexed membership. It can change when failure becomes visible without correcting the selection policy.
Ask the learner to propose a repair and identify what it stabilises. If the repair stabilises only the number five, it has not answered which object index one will mean after A leaves. The operation needs a coherent traversal strategy for changing membership, not merely a different source for the upper-bound number.
CHAPTER 6 OF 18 · Compare removal strategies
6. Work backwards under the stated removal rule
For the original restricted task, traverse the current collection from its last index towards zero. Start at four, obtain E and remove it. The remaining collection is A, B, C, D. None of the earlier indices changes because the removed member was after them.
Then index three gives D, index two gives C, index one gives B and index zero gives A. Under the stated assumption that each iteration only removes its selected element, all five original identities are processed once and the container becomes empty.
The reason is structural. Removing the last currently targeted member does not shift the indices that the loop will visit next. This is a proof about the constrained mutation pattern. It is not a claim that reverse traversal is immune to arbitrary code running inside the loop.
Change one condition. Suppose processing E also removes B. The next earlier positions may no longer describe the same intended sequence. If processing inserts an item before the next index or moves a card to another position, the simple argument needs to be reconsidered.
State the operation's invariant in ordinary language: after removing the selected last member, all members still due for processing keep their earlier indices. If a callback can violate that sentence, do not call the reverse-loop pattern universally safe. Use an explicit work set or another design that matches the actual mutation policy.
CHAPTER 7 OF 18 · Compare removal strategies
7. Repeatedly remove the first current member
Another strategy for emptying the dedicated container is to check whether the live collection has a member, remove its current first member and repeat. There is no increasing index to advance past a newly shifted element.
The original sequence is A, B, C, D, E. Removing the current first member gives B, C, D, E; then C, D, E; then D, E; then E; then an empty collection. Each step removes a member that actually exists at the start of that step.
For the restricted exercise, termination has a simple measure: the number of remaining members decreases by one on every iteration and cannot fall below zero. Starting at five, the operation needs five removals. The measure explains why this loop finishes; the syntax alone does not.
If processing one card adds another matching card, that decreasing measure no longer holds. The loop may process newly added work as well as the original five, and under some application rules it may never reach an empty collection. That behaviour might violate the learner's expectation that clear applies only to the starting set.
Choose this strategy because the desired operation is to drain the current container under controlled rules. Do not use it as a substitute for deciding which cards belong to this action. For user-facing work, an explicit starting set can be more appropriate when cards arriving later should remain untouched.
querySelectorAll returns a static selection result. For a container with the five matching card descendants, selecting them at the start yields references to A, B, C, D and E. Removing A from the document does not make that selected list replace its first member with B.
Iterating that starting selection therefore continues to B next, provided the code follows the list in order. After B is removed, it continues to C, then D and E. The work set remains the originally selected five identities even though the document is changing around them.
Static membership does not mean the elements are frozen copies. If another operation changes B's text before B is processed, the reference still points to B with its changed state. If another operation removes C first, the list can still contain a reference to C after it leaves the original container.
That distinction is essential for consequential operations. The application may need to check that an originally selected card still belongs to the intended container or is still eligible before acting. A static list gives a stable selection, not automatic permission to perform every later operation on every retained reference.
The selection expression itself also needs review. A broad descendant selector can include nested cards. A direct-child selector has a different scope. Keep the chosen scope explicit and test it in the supported environment. Fixing liveness while selecting the wrong elements replaces one bug with another.
CHAPTER 9 OF 18 · Choose the work set
9. Copy a live collection without copying its elements
An application can also materialise a sequence of element references from a live collection, for example with Array.from at a deliberate stage. For an ordinary controlled copy of the initial collection, the resulting array contains A, B, C, D and E.
After the copy, removing A changes the original live collection to B, C, D, E. The array still has A at index zero and B at index one. Its length does not shrink merely because the document's matching set shrank.
The array contains references, not cloned DOM subtrees. A later change to B can be observed through the array's B reference. Removing A does not require deleting its array entry, and deleting an array entry is not itself a command to remove a DOM element.
Give these structures different names. currentCards can mean the live view; selectedCards can mean the starting array. If both are called cards in separate functions, a reader may not see where membership stops being live.
The copying stage is part of the contract. Materialising a sequence at the start expresses a decision to process that starting set. A copy made later can contain a different set. More elaborate iteration, mapping callbacks or custom objects can introduce additional side effects; the simple paper trace assumes the copy operation is not accompanied by code that mutates the tree while the selection is being materialised.
Removal is not the only way a live filtered collection can shrink. Suppose all five cards carry the class pending, and the application obtains a live collection through getElementsByClassName('pending'). Processing a card removes that class while leaving the element in the document.
Start with the matching view A, B, C, D, E. At index zero, remove pending from A. A remains visible according to the example's styles, but it no longer matches the filter. The live collection becomes B, C, D, E.
Increasing the index to one now selects C. Removing pending from C leaves B, D, E in the view. The next index two selects E. The original forward-loop mistake therefore reappears even though no element was removed from the tree.
This case tests whether the learner understood reindexing or memorised a rule about remove(). The source of reindexing is a changed matching set. A class mutation can change that set just as a tree removal can.
Keep document state and selection membership in separate columns. A can be connected to the document and absent from the pending collection at the same time. A report saying the card disappeared should specify whether it disappeared from the screen, the container, the class-filtered view or an application-owned work set. Those outcomes have different causes and different repairs.
CHAPTER 11 OF 18 · Change the matching set
11. Separate visibility from matching membership
Suppose processing a pending card changes only its presentation, making it hidden through a CSS rule while leaving the pending class in place. The class-filtered collection continues to include that element. A visual disappearance does not automatically remove it from a collection defined by a different filter.
For an original comparison, create A, B and C as matching cards. In case one, remove pending from A. The pending collection becomes B, C. In case two, keep pending but set a separate hidden presentation state on A. Under that class filter the collection remains A, B, C.
The same screenshot can therefore accompany different matching sets. Conversely, the matching set can change without a dramatic visual change if the removed class has no relevant presentation rule. Inspect the filter and underlying tree rather than inferring membership from appearance.
The distinction helps diagnose loops that keep selecting a supposedly removed card. If a drain loop repeatedly chooses the first member and only hides it, the collection may never shrink. The progress measure from the removal exercise has been broken: there is no longer a guarantee that length decreases.
Do not solve that problem with an arbitrary iteration cap and declare success. A cap can prevent runaway work, but it does not establish that the intended cards were processed. Decide whether the operation is hiding, removing, reclassifying or recording completion, then choose a collection and termination condition consistent with that operation.
CHAPTER 12 OF 18 · Change the matching set
12. Insert a member while traversal is underway
Return to a live view A, B, C. Suppose processing A inserts a new matching card X before B. After the insertion, the collection is A, X, B, C if A remains in the view. Increasing an index from zero to one selects X next.
That can be the intended behaviour for an operation that processes the current evolving view. It can be surprising for an operation whose promise was to handle only the three cards present when the action began. The loop did not gain a mysterious extra iteration; the selected set gained a member under its live contract.
Now make the handler insert a new matching card before the current one. Index positions can change in a way that makes the next increment revisit an already processed identity or miss another. There is no universal forward-loop result without specifying where insertion occurs and which members remain matching.
A static starting selection gives a different policy: X is not retroactively added to that earlier result. If X should be handled, the application needs an explicit later selection or queue rule. If X should wait for the next action, excluding it may be exactly right.
Describe that choice in reader terms. Clear the temporary cards that were selected at the start is understandable. Keep clearing every newly arriving card may be a different and potentially undesirable interaction. A technical traversal strategy should implement the promised scope rather than quietly choosing it on the user's behalf.
CHAPTER 13 OF 18 · Change the matching set
13. Reorder members without changing the count
A constant length does not establish stable indexed membership. Start with A, B, C, D. While processing A at index zero, suppose code moves A to the end of the same container. The live order becomes B, C, D, A and the length remains four.
Increasing the index to one now selects C. B has moved behind the traversal position even though no member was added or removed. A may appear again later depending on the rest of the operations. This is the same positional hazard expressed through reordering rather than shrinking.
The count-based diagnostic fails here. A log that says length stayed four throughout can be accurate and still miss the cause of skipped work. Retain the ordered identities when the operation can reorder the tree.
For a changed-context test, keep the original order but update only A's text. The collection's order need not change, so increasing to index one still selects B under the stated rules. The important difference is not that some DOM mutation occurred. It is whether that mutation changed the membership or order used by the next indexed access.
This gives a more precise review question than does this loop mutate the DOM? Ask whether anything in the loop can change the view that supplies its next member. If yes, prove the traversal against those changes or materialise the intended work set before acting.
CHAPTER 14 OF 18 · Change the matching set
14. Keep static membership separate from current eligibility
Suppose a static starting selection contains A, B and C. Processing A triggers application logic that moves B into a saved-notes region. When the traversal reaches B, the stored selection still contains the B reference.
Whether B should now be removed depends on the operation's promise. If the action was to remove the original selected cards wherever they move, the retained reference may match that policy. If the action was only to clear temporary cards still owned by the practice container, removing B from saved notes would violate its scope.
A membership snapshot cannot decide that application question. Code may need to check current parentage, an ownership marker or another explicit eligibility condition before acting. No single field is universally sufficient: a card can remain connected while moving to a region the operation must not touch.
For the paper ledger, give each row an original-selection column and a current-eligibility column. A is selected and eligible. B is selected but no longer eligible under the container-only policy. C is selected and eligible. The correct processed set is then A and C, with B deliberately skipped and a reason retained.
This is different from the accidental omission in the forward live loop. An accidental skip lacks a policy explanation; a deliberate skip follows a current eligibility rule. Keep those results distinct when testing. A final count of two processed cards cannot by itself tell which kind of skip occurred.
CHAPTER 15 OF 18 · Verify the traversal contract
15. Treat callbacks as possible changes to the traversal contract
A helper called processCard can look harmless at the loop site while hiding removal, insertion or reordering. Event dispatch, application hooks and component lifecycle behaviour can also invoke other code during a larger operation. Review the actual mutation boundaries relevant to the implementation.
Do not assume every mutation observer callback runs immediately inside the remove statement. DOM mutation notifications have their own delivery rules. The existing MutationObserver guides explain queued records and removed-subtree coverage. Here, the question is which code can change the selection before the next access in this particular traversal.
Separate direct helper effects from later queued work in the trace. If processCard synchronously removes another member, record that before increasing the index. If an observer runs later, put it at its actual delivery stage rather than inserting it into the paper sequence because its name contains observer.
For a minimal learning case, initially make the helper remove only its argument. Then add one documented side effect: processing E also moves B. Recheck the reverse-loop invariant. A strategy that was valid under the original rule is not disproved by this change; its assumptions have changed.
The useful repair is a clear interface between traversal and processing. Document what processing may change, choose a work-set policy and make any eligibility checks explicit. That is more dependable than expecting a clever loop direction to compensate for arbitrary hidden mutation.
CHAPTER 16 OF 18 · Verify the traversal contract
16. Choose a strategy from the intended set
For a bounded starting set, a static selection or explicitly materialised reference array often makes the operation easier to reason about. It identifies the candidates once. Eligibility can then be checked at the point of action without letting an evolving view silently redefine the starting set.
For controlled emptying of a dedicated container, repeatedly removing its current first member can express the drain operation directly. The termination argument must still hold: each iteration removes a current member, and processing does not replace it with endless matching work.
For a live collection where each body only removes its own selected member, reverse traversal has a clear index-preservation argument. Its correctness depends on the restricted mutation pattern. If other members can move or disappear, re-evaluate that argument.
None of these choices is a universal best loop. They answer different questions about what belongs to the action. A dedicated container with only disposable practice cards is a simpler object than an entire document containing saved work, navigation and dynamically inserted components.
Before implementation, write one sentence naming the set and one sentence naming permitted mutations. For example, process the five selected temporary cards that remain owned by this container; processing may remove the current card but cannot add another candidate. Those sentences make both the code review and the changed-context tests more precise.
CHAPTER 17 OF 18 · Verify the traversal contract
17. Build a reproduction that exposes each access
Use a small owned page with five uniquely labelled fictional cards. Obtain the collection through the exact API being investigated. Log the current ordered identities before each indexed access, the identity selected, the operation performed and the resulting order.
Run the forward live-removal case first. Predict A, C, E before inspecting the result. Restore all five cards before running reverse traversal, a first-member drain and a static starting selection. Restoration matters: running a second strategy on the leftovers of the first does not compare the same starting condition.
Then change only one mechanism. Remove a matching class instead of removing the node. Hide a node without changing the matching class. Insert X. Move A to the end. Each case should have a separately predicted membership ledger.
The expected identity sequence is more informative than elapsed time or one final screenshot. A visually empty container can conceal that the wrong region was cleared. A correct count can conceal that one card was processed twice while another was missed. Keep identity, membership and application eligibility together.
The examples in this article are paper traces, not browser-execution receipts. For an actual repair, record browser and application versions and use the intended DOM structure. If the observed result differs, inspect root, filter, callback effects and timing. Do not adjust the predicted list after the fact without explaining the missing condition.
CHAPTER 18 OF 18 · Verify the traversal contract
18. Explain the skipped cards through the changing view
A useful bug report can be short and exact: the live collection began A, B, C, D, E. Removing A shifted B into index zero. The loop advanced to index one and selected C. It then selected E and stopped with B and D remaining.
That account identifies the mechanism without blaming the loop counter for doing what it was written to do. The counter increased correctly; the view also changed correctly. Their combination failed to implement the intended starting-set operation.
An effective repair note names its new policy. The operation now materialises the starting candidates and rechecks container ownership before removal is stronger than we changed the loop. The policy explains what will happen if a card is added or moved during processing.
For teaching, ask the learner to predict the class-removal case and the constant-length reordering case before showing any answer. A learner who can explain both has moved beyond memorising the every-other-card pattern. They understand that a position belongs to a current view, while an object retains its identity across view changes.
Carry that distinction into maintenance. When a helper later gains permission to insert cards or transfer them between containers, revisit the traversal assumptions. A previously justified loop can become unsuitable because its surrounding contract changed. Keep the intended set visible so that an ordinary feature change does not silently turn a controlled cleanup into accidental omission or excessive removal.
Continue through the verified reading route
Stored geometry and current elements
Stored geometry and current elements. Compare a non-live measurement object with a changing element; keep that job separate from changing collection membership.
Mutation records after subtree removal
Mutation records after subtree removal. Continue here for queued mutation delivery and temporary observation coverage after removal.
Registered listener identity and options
Registered listener identity and options. Keep callback identity and registration ownership explicit when processing invokes event-driven code.
Browser layout and rendering mathematics
Browser layout and rendering mathematics. Connect DOM tree structure to the wider rendering model.
Copied structure and instance behaviour
When a copied control looks complete but responds differently, follow separating cloned markup from registered event listeners. The practice-card traces distinguish new node identities, target-owned registrations, delegation, captured references and instance setup.
Connected practical checks
The Gold Standard of Websites | Separating ArrayBuffer Transfer from Copying Its Bytes
The Gold Standard of Websites | Separating ArrayBuffer Transfer from Copying Its Bytes. Distinguish a copied work set of references from copied backing bytes and transferred binary-storage ownership.
The Gold Standard of Websites | Tracing Array.from Mapping Before Source Consumption Finishes. Compare mapping during consumption with mapping a completed sequence, including iterators and changing source values.
For a separate keyed-collection contract, follow tracing Map visits when entries are deleted and reinserted. The worked histories distinguish pending deletions, existing-value updates, new insertion positions, repeated key labels and fixed work sets.
Frequently asked questions
Is every NodeList static?
No. The API producing the collection determines its contract. querySelectorAll returns a static result, while childNodes is a live collection. Do not infer liveness merely from the interface name or from whether the value looks convenient to iterate.
Does saving collection.length freeze its members?
No. A number records a count. Indexed accesses on a live collection still use its current membership and order. A fixed upper bound can therefore encounter missing positions or skip intended objects after mutation.
Does a static selection contain cloned elements?
No. Its members are references to selected nodes. Their attributes, text, connectivity and application ownership can change. Stable selection membership does not establish that every member remains eligible for a later action.
Is reverse traversal always safe?
No. It has a clear argument when the operation only removes the current selected member and does not disturb earlier positions. Insertion, reordering or removal of other members can invalidate that argument. Review the permitted mutations.
Why did hiding the first member make my drain loop repeat?
If the collection's filter still matches that element, hiding it does not necessarily reduce membership. The loop can select it again. Match the progress measure to the actual operation: hiding, reclassifying and removing are different changes.
What should be verified before using a cleanup action with real work?
Check the selection's root and filter, starting-set policy, current eligibility, callback effects, termination and the visitor's recovery path. The paper identity ledgers explain traversal; they do not replace testing of the implemented interaction or the product decision about what may be removed.
Authoritative sources and further reading
WHATWG DOM — collections. Defines roots, filters, tree order and live versus static views; consulted 29 October 2026.
WHATWG DOM — querySelectorAll. Defines the static selection result. The worked ledgers are original, not browser execution receipts.
WHATWG DOM — children and childNodes. Use the ParentNode contract together with the Node childNodes contract to identify the selected kinds of nodes.
ECMAScript — Array.from. Defines array materialisation; a copied reference sequence does not clone DOM elements.
Return to the Websites subject guide, The Gold Standard of Education, or the How X Works hub.