cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Walking & searching the DOM

Learn to find elements by walking parent, child, and sibling relationships, searching with CSS selectors, using closest and matches, and choosing between live and static DOM collections.

By the end, you can
  • 01
    Walk the treeUse parent, child, and sibling properties without being surprised by text nodes.
  • 02
    Search with selectorsUse querySelector, querySelectorAll, matches, and closest accurately.
  • 03
    Choose collection APIsTell live HTMLCollections from static NodeLists and avoid loop-removal bugs.

Find your place in the tree

The DOM is a tree. Once JavaScript has one node in that tree, it can either walk by relationships or search by a selector. Walking means “go to my parent,” “go to my first child,” or “go to the next sibling.” Searching means “find the first element that matches this CSS selector” or “find all matches.”

This lesson builds on The browser environment and The DOM tree. You will use the same mental model, but now you will move around it on purpose: from a clicked button to its card, from a menu to its items, and from a list of matching elements to safe loops.

Real-life analogyWalking a family tree

Imagine you are sitting at a family dinner. You can point to your parent, your children, or the person next to you without searching the whole room. DOM walking is that local: start from one node and follow a relationship.

In real life: Go up to your parent
In JavaScript: parentElement
In real life: Look at the kids
In JavaScript: children, firstElementChild, lastElementChild
In real life: The person sitting next to you
In JavaScript: nextElementSibling or previousElementSibling

Where the analogy stops: Families have names and stories; DOM nodes are just objects linked by parent/child/sibling references. Also, node properties can include text nodes that do not feel like family members at all.

The short definition

DOM navigation means finding nodes by relationship (parentElement, children, siblings) or by a selector (querySelector, closest, and friends).

The biggest gotcha is the difference between elements and nodes. Elements are tags like ul and li. Nodes also include whitespace text and comments. The names tell you which world you are in: children and firstElementChild are element-only; childNodes and firstChild include all node types.

Parents, children & siblings

INTERACTIVE

Relationship properties are the fastest way to move when you already have a nearby node. They do not parse selector strings. They simply follow links the browser already keeps in the tree.

Element-only walking vs all-node walking
GoalElement-only propertyAll-node property
Go upparentElement → an element or nullparentNode → any parent node, possibly the document
Go downchildren, firstElementChild, lastElementChildchildNodes, firstChild, lastChild
Go sidewaysnextElementSibling, previousElementSiblingnextSibling, previousSibling
Loop safelyGreat for tags you can style or clickUse when text/comment nodes matter

A professional detail worth knowing: document.documentElement is the html element. Its parentNode is the document, but its parentElement is null, because a document is a node, not an element.

Step through one walk
Step 0 of 5Ready
Your turn: follow the blue line

Switch between element-only and all-node walking, then step through the same tree.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
let current = list.firstElementChild;current = current.nextElementSibling;current = current.parentElement;console.log(current.id);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
Choose which family of properties the code uses.

Changing the mode starts a fresh replay. The all-node version can land on whitespace.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Now try the same idea on real nodes created in your browser. The buttons with Element in their names skip whitespace. The shorter node properties can land on a blank text node because the markup has line breaks and indentation between elements.

DOM navigator: walk the tree
Navigation codePop out in the code editor (opens in a new tab)JavaScript
const list = document.querySelector("#menu");let current = list.firstElementChild;current = current.nextElementSibling;current = current.parentElement;current = current.parentNode;current = current.firstChild;current = current.nextSibling;
Mini pageul#menu
Expression chainlist
Try it yourself

Start on the menu. Element buttons skip whitespace; node buttons reveal it.

The mini-page is created with DOM APIs inside an empty container, so React does not own the nodes you move through.

querySelector & querySelectorAll

INTERACTIVE

When you do not already have a nearby node, search by CSS selector. querySelector returns the first matching element or null. querySelectorAll returns all matching elements in a static NodeList. That list has forEach, and it does not update when the page changes later.

Real-life analogyA librarian with one book or every book

If you ask a librarian for “the first red JavaScript book,” you get one book. If you ask for “every red JavaScript book,” you get a stack. Selectors work the same way: one match or all matches.

In real life: Ask for the first matching book
In JavaScript: querySelector(selector)
In real life: Ask for every matching book
In JavaScript: querySelectorAll(selector)
In real life: Bad description that is not valid library language
In JavaScript: Invalid selector → SyntaxError

Where the analogy stops: A librarian may ask clarifying questions. The browser will not: a valid selector with no matches gives null or an empty list, and an invalid selector throws immediately.

Search methods exist on both document and elements. Element searches are scoped to descendants of that element, but the selector is still interpreted as a normal CSS selector. For direct children of the current element, use :scope > li. Without :scope, selectors such as div p can be surprising because the selector pattern is matched in the larger tree before descendants are filtered.

Selector tester: ask the librarian
Searching codePop out in the code editor (opens in a new tab)JavaScript
const menu = document.querySelector("#menu");const firstDone = menu.querySelector(".done");const allItems = menu.querySelectorAll("li");const directItems = menu.querySelectorAll(":scope > li");const dataItems = menu.querySelectorAll("[data-id]");
Matchesbase: mini page
Try it yourself

3 matches. querySelector marks the first one.

querySelector gives the first match or null. querySelectorAll gives a static NodeList of every match and throws only for invalid selector syntax.
Tiny but important API map
  • document.getElementById("id") exists on document, DocumentFragment, and ShadowRoot, not ordinary elements.
  • document.getElementsByName("field") is document-level.
  • Elements do have querySelector and querySelectorAll.

closest & matches

INTERACTIVE

Two selector methods live on elements themselves. matches(selector) returns a boolean: does this exact element match? closest(selector) starts with the element itself, then walks upward through parents until a match is found. If nothing matches, it returns null.

Real-life analogyWalking up your own family line

Suppose every table has a host. You start with yourself: “Am I the host?” If not, you ask your parent, then grandparent. That is closest: it checks the current element first, then moves up.

In real life: Check whether you are the person named on the invitation
In JavaScript: el.matches(selector)
In real life: If not, ask your parent, then grandparent
In JavaScript: el.closest(selector)
In real life: No one in your line matches
In JavaScript: null

Where the analogy stops: closest walks only through ancestors. It never searches siblings, children, cousins, or the whole page.

This is especially useful for event delegation. A click may land on an icon, a span, or text inside a button. Instead of wiring every tiny piece, you start from event.target and climb to the button or card that matters.

closest & matches: walk upward from a click
Delegated click codePop out in the code editor (opens in a new tab)JavaScript
button.addEventListener("click", (event) => {  const element = event.target;  const isItem = element.matches("li");  const row = element.closest("li");  const menu = element.closest("#menu");});
Click targetgreen = closest result
Try it yourself

Click any element in the mini-page, then change the selector.

matches answers yes/no for the clicked element. closest starts with that same element, then walks through parents until a selector matches.

getElementsBy*

Before selector APIs were common, browsers had methods such as getElementsByTagName and getElementsByClassName. They still exist, and you will see them in older code and performance- sensitive code. Their biggest difference is not the search syntax; it is the collection they return.

DOM search methods compared
MethodReturnsCollection behavior
document.querySelector(selector)First matching element or nullNo collection
document.querySelectorAll(selector)Static NodeList of elementsSnapshot; has forEach
element.getElementsByTagName(name)Live HTMLCollectionUpdates automatically; no forEach
element.getElementsByClassName(name)Live HTMLCollectionUpdates automatically; no forEach
document.getElementsByName(name)Live NodeList in browsersDocument-level name search

HTMLCollection is iterable in modern browsers, so for...of works, but it does not have forEach. Convert with Array.from(collection) when you want array methods or a safe snapshot.

Live vs static collections

INTERACTIVE
Real-life analogySecurity-camera feed vs photograph

A security camera shows the room right now. If someone walks in, the count changes. A photograph stays the same even if the room changes. Live collections are the camera; static lists are the photograph.

In real life: Live security-camera feed
In JavaScript: Live HTMLCollection from getElementsBy*
In real life: A photograph taken once
In JavaScript: Static NodeList from querySelectorAll
In real life: People leave while you count
In JavaScript: Removing from a live collection while looping

Where the analogy stops: A live collection is not a video file you can rewind. It is an object whose contents are recalculated from the current DOM whenever you look at it.

Live collections can be convenient, but they are dangerous when you remove elements while looping forward. Removing item 0 makes item 1 slide into index 0, then the loop increments to index 1 and skips it. Use Array.from(live), use querySelectorAll, or loop backward.

Live vs static: camera feed or photograph?
Collection codePop out in the code editor (opens in a new tab)JavaScript
const liveItems = menu.getElementsByTagName("li");const staticItems = menu.querySelectorAll("li");menu.append(document.createElement("li"));console.log(liveItems.length);console.log(staticItems.length); for (const item of liveItems) {  item.remove();}
Listwatch the lengths
Try it yourself

Live length: 3. Static length: 3.

getElementsByTagName keeps watching the page. querySelectorAll is a snapshot taken at the moment you call it.
Live or static? Elements only or all nodes?
  • element.children
  • document.getElementsByClassName("done")
  • menu.getElementsByTagName("li")
  • menu.querySelectorAll("li")
  • element.childNodes
  • element.parentElement
  • button.closest("li")
  • document.querySelector(".done")
Try it yourself
0 of 8 correct

Classify each API by the kind of value it returns.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Where you’ll use this

DOM navigation appears in real interfaces whenever code starts from a small event target and needs the larger piece of UI around it. A todo app might click a delete icon and find the todo row. A menu might click a nested span and find the button. A product grid might click “Save” and find the card id.

Common event-delegation helperPop out in the code editor (opens in a new tab)JavaScript
function findCardAction(event) {  const button = event.target.closest("button[data-action]");  if (!button) return null;   const card = button.closest("[data-card-id]");  return {    action: button.dataset.action,    cardId: card?.dataset.cardId ?? null,  };}

Notice the defensive shape: search upward, bail out with null when nothing matches, then read dataset from the element that actually owns the data. This keeps the handler working even if the button later gains an icon or extra wrapper.

Common misconceptions

  • “Child means element.” Not always. firstChild may be whitespace text; use firstElementChild for tags.
  • “No selector match throws.” A valid selector with no match returns null or an empty list. Invalid selector syntax throws SyntaxError.
  • “NodeList means static.” querySelectorAll returns a static NodeList, but childNodes is a live NodeList.
  • “HTMLCollection is an array.” It is array-like and iterable, but it lacks array methods such as forEach.
  • “closest searches inside the element.” It checks the element itself and ancestors only.
  • “Element search is always perfectly scoped.” Use :scope when the selector must refer to the element you called from.

Practice exercises

5 EXERCISES
Exercise 1 · Warm-upPredict live vs static lengths

Type the exact output. This mirrors the live/static length display in the playground.

Starter codePop out in the code editor (opens in a new tab)JavaScript
const liveLength = 3;
const staticLength = 2;
console.log(liveLength + " / " + staticLength);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeChoose a scoped selector

    Which selector finds direct li children of the current element?

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFind the bug in a removal loop

      Name one safe fix for this live-collection loop.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const items = list.getElementsByTagName("li");
      for (let i = 0; i < items.length; i += 1) {
        items[i].remove();
      }

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeUse the upward method

        Which method walks upward until a selector matches?

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeWrite a delegated menu handler

          Write the handler in your editor or console. It should ignore clicks outside action buttons.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          menu.addEventListener("click", (event) => {
            // Find a button with data-action, then find its li.
          });

            Check your understanding

            8 QUESTIONS
            Walking & searching quiz · 8 questionsScore: first tries count
            1. Question 1 of 8What does parentElement return when there is no parent element?

              Choose an answer to see the explanation.

            2. Question 2 of 8Which property skips whitespace text nodes?

              Choose an answer to see the explanation.

            3. Question 3 of 8What does this print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const found = null;
              console.log(found === null);

              Choose an answer to see the explanation.

            4. Question 4 of 8Which statement about querySelectorAll is accurate?

              Choose an answer to see the explanation.

            5. Question 5 of 8What does closest check first?

              Choose an answer to see the explanation.

            6. Question 6 of 8What length does this skipped-list snippet print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const skipped = ["Home", "About"];
              console.log(skipped.length);

              Choose an answer to see the explanation.

            7. Question 7 of 8Which API exists on document, DocumentFragment, and ShadowRoot, but not ordinary elements?

              Choose an answer to see the explanation.

            8. Question 8 of 8How do you make element.querySelectorAll mean direct children of that element?

              Choose an answer to see the explanation.

            Key takeaways

            • Use element-only walking properties when you want tags, not whitespace text nodes.
            • querySelector returns the first match or null; querySelectorAll returns a static NodeList.
            • matches tests one element; closest checks that element and then its ancestors.
            • getElementsByTagName, getElementsByClassName, and children are live element collections.
            • Snapshot live collections before removing, or loop backward.

            DOM navigation is choosing the right path through the page tree: local relationships when you are nearby, selector searches when you need to find matches.

            Up next: Node properties & contents.

            CompleteFrontend Clear concepts. Working examples.