W3docs

JavaScript Searching: getElement*, querySelector*

Exploring and mastering the Document Object Model (DOM) is crucial for JavaScript developers aiming to build interactive and dynamic web applications. This

Before you can change, move, or read anything on a page, you have to find the element you want. This is the very first step of nearly every DOM task, and JavaScript gives you two families of tools for it:

  • Legacy getElement* methodsgetElementById, getElementsByClassName, getElementsByTagName. They are fast and return live collections.
  • Modern querySelector* methodsquerySelector, querySelectorAll. They accept any CSS selector and return static results.

This guide covers both families, the matches, closest, and contains helpers for checking and walking the tree, and the single most important gotcha: the difference between a live collection and a static one. Each example is runnable, so you can see the result immediately.

Once you can find an element, the next steps are usually traversing the DOM to reach its neighbours and manipulating the DOM to change it. For a gentler overview, see selecting DOM elements.

Efficient Element Access: getElementById

The getElementById method is the fastest and most reliable way to access a single element, because an ID is supposed to be unique within a document and browsers index IDs internally. It returns the matching element, or null if no element with that ID exists — so guard against null before using the result. Note that you pass the bare ID, without a leading # (that's only for CSS selectors). In the example below, the initial "Default text" is immediately replaced.

<!-- snippet: html-result -->

<!DOCTYPE html>
<html>
<head>
    <title>getElementById Example</title>
</head>
<body>
    <div id="main-content">Default text</div>
    <script>
        const element = document.getElementById('main-content');
        element.innerHTML = "Modified text!"
    </script>
</body>
</html>

Accessing Multiple Elements: getElementsByClassName and getElementsByTagName

Info

When you select elements by class name or tag name, you get back an HTMLCollection. This is a live collection: it updates automatically as the DOM changes. It is array-like — you can read elements by index (els[0]) and check els.length — but it is not a real array, so it has no forEach, map, or filter. To iterate it safely, convert it first with Array.from(...) (or the spread [...els]).

Example Using getElementsByClassName

Access multiple elements with the same class using getElementsByClassName. In this example we have two div elements with the same class name. We modify both of them by selecting those elements by their class name.

<!-- snippet: html-result -->

<!DOCTYPE html>
<html>
<head>
    <title>getElementsByClassName Example</title>
</head>
<body>
    <div class="info">First Info</div>
    <div class="info">Second Info</div>
    <script>
        const infoElements = document.getElementsByClassName('info');
        Array.from(infoElements).forEach(el => el.innerHTML = "MODIFIED!");
    </script>
</body>
</html>

Example Using getElementsByTagName

Retrieve elements by their tag name with getElementsByTagName. It's completely similar to the previous one, but this time we select by the tag name, not the class name.

<!-- snippet: html-result -->

<!DOCTYPE html>
<html>
<head>
    <title>getElementsByTagName Example</title>
</head>
<body>
    <p>First Paragraph</p>
    <p>Second Paragraph</p>
    <script>
        const paragraphs = document.getElementsByTagName('p');
        Array.from(paragraphs).forEach(el => el.innerHTML = "MODIFIED!");
    </script>
</body>
</html>

Flexible Searches with querySelector and querySelectorAll

Selecting with querySelector

Use querySelector to find the first element matching a CSS selector. In this example, we select the first element with the class text that is a direct child of the element with the main id.

<!-- snippet: html-result -->

<!DOCTYPE html>
<html>
<head>
    <title>QuerySelector Example</title>
</head>
<body>
    <div id="main"><span class="text">This will be replaced</span></div>
    <div id="other"><span class="text">This one doesn't change</span></div>
    <script>
        const spanInsideDiv = document.querySelector('#main > .text');
        spanInsideDiv.innerHTML = "MODIFIED!";
    </script>
</body>
</html>

Retrieving Multiple Elements with querySelectorAll

querySelectorAll returns all elements that match a CSS selector, as a static NodeList. Conveniently, a NodeList does have a built-in forEach, so you can loop it directly without converting to an array first.

The word static is important: querySelectorAll takes a snapshot of the matches at the moment you call it. If you add or remove matching elements afterwards, that snapshot does not change. This is exactly the opposite of the live HTMLCollection returned by the getElementsBy* methods.

<!-- snippet: html-result -->

<!DOCTYPE html>
<html>
<head>
    <title>QuerySelectorAll Example</title>
</head>
<body>
    <ul>
        <li class="item">Item 1</li>
        <li class="item">Item 2</li>
    </ul>
    <script>
        const items = document.querySelectorAll('.item');
        items.forEach(item => item.innerHTML = "MODIFIED!");
    </script>
</body>
</html>

Live vs. Static: the Collection Gotcha

This is the trap that bites most beginners. A live HTMLCollection reflects the current state of the DOM at every read, while a static NodeList is frozen at the moment of selection. The snippet below shows both reacting to a newly added element:

// Suppose the page has two <li class="item"> elements.
const live = document.getElementsByClassName('item');   // live HTMLCollection
const snapshot = document.querySelectorAll('.item');     // static NodeList

console.log(live.length);     // 2
console.log(snapshot.length); // 2

// Now add a third matching element.
const li = document.createElement('li');
li.className = 'item';
document.querySelector('ul').appendChild(li);

console.log(live.length);     // 3  — updated automatically
console.log(snapshot.length); // 2  — still the old snapshot

Why it matters: looping over a live collection while removing matching elements is a classic source of skipped items, because the collection shrinks under you. A static NodeList from querySelectorAll is safer for that case, since the list won't change mid-loop.

Checking and Walking: matches, closest, and contains

Searching isn't only about finding elements — often you have an element and need to ask a question about it.

  • element.matches(selector) returns true if the element itself matches the CSS selector. Great for event delegation.
  • element.closest(selector) walks up the tree from the element (including itself) and returns the nearest ancestor that matches, or null.
  • parent.contains(node) returns true if node is the parent itself or a descendant of it.

<!-- snippet: html-result -->

<!DOCTYPE html>
<html>
<body>
    <section class="card">
        <button id="save" class="btn primary">Save</button>
    </section>
    <div id="out"></div>
    <script>
        const btn = document.getElementById('save');
        const section = document.querySelector('.card');
        const out = document.getElementById('out');

        out.innerHTML =
            'matches(".primary"): ' + btn.matches('.primary') + '<br>' +
            'closest(".card") is section: ' + (btn.closest('.card') === section) + '<br>' +
            'section.contains(btn): ' + section.contains(btn);
    </script>
</body>
</html>

Which Method Should I Use?

  • Need one element by ID? Use getElementById — it's the fastest and clearest.
  • Need a CSS selector (descendants, combinators, attributes, :not())? Use querySelector / querySelectorAll.
  • Need a live list that tracks DOM changes? Use getElementsByClassName / getElementsByTagName.
  • Have an element and need to test or climb the tree? Use matches, closest, or contains.

Conclusion

Finding elements is the foundation of every DOM script. Reach for getElementById for single IDs, querySelector* for the flexibility of CSS selectors, and the getElementsBy* methods when you genuinely need a live collection — just remember the live-vs-static difference so collections don't surprise you mid-loop. From here, continue with traversing the DOM and DOM manipulation.

Practice

Practice
Which of the following statements are correct about JavaScript's querySelector and getElementById methods?
Which of the following statements are correct about JavaScript's querySelector and getElementById methods?
Was this page helpful?