Selecting DOM Elements in JavaScript: Full Guide

Learn every way to select DOM elements with JavaScript: getElementById, querySelector, querySelectorAll, and live collections. See which method fits each situation.

5 min read

To do anything with the DOM, you first need to find the right element. JavaScript gives you five methods to select DOM elements, each designed for a different situation. The right choice depends on whether you are targeting one element or many, whether you know the element's ID, and whether you need a snapshot or a collection that updates itself automatically.

Here is a quick comparison of all five methods:

MethodReturnsLive?Select by
getElementByIdSingle element or nullN/AID
querySelectorFirst match or nullNoAny CSS selector
querySelectorAllStatic NodeListNoAny CSS selector
getElementsByClassNameLive HTMLCollectionYesClass name
getElementsByTagNameLive HTMLCollectionYesTag name

The table gives you the facts at a glance, but choosing the right method also depends on your specific task. The decision flow below covers the most common scenarios:

Choosing a DOM selection method

The flow shows that when you know the ID, getElementById is the answer. When you need the first match for any CSS selector, use querySelector.

When you need every match and a static list is fine, querySelectorAll is the right choice. Only when you specifically need a live collection that stays in sync with the page should you reach for getElementsByClassName or getElementsByTagName.

getElementById: The Fastest Single-Element Lookup

When an element has a unique id attribute, getElementById is the fastest and most direct way to select it. The browser maintains an internal lookup table of all IDs, so this method finds the element almost instantly.

htmlhtml
<button id="submit-btn">Submit</button>

This button has a unique ID that makes it easy to target. The getElementById method uses the browser's internal ID lookup to find it instantly:

javascriptjavascript
const button = document.getElementById("submit-btn");
console.log(button.textContent);

The output is the string "Submit". The method returns the single element with that ID, or null if no match exists.

Since IDs must be unique on a page, you will always get at most one element back. Always check for null before using the result, especially in shared code where you cannot guarantee the element exists.

querySelector: One Element, Any CSS Selector

The querySelector method is the most flexible of the five. It accepts any valid CSS selector and returns the first element that matches. You can select by tag name, class, ID, attribute, pseudo-class, or any combination you would write in a stylesheet.

Here are examples covering the most common patterns:

javascriptjavascript
const heading = document.querySelector("h1");
const intro = document.querySelector(".intro");
const button = document.querySelector("#submit-btn");
const emailInput = document.querySelector('input[type="email"]');
const firstItem = document.querySelector("ul.menu > li:first-child");

Each line finds one element using a different kind of CSS selector. If no element matches, querySelector returns null, not an error. This means you should always guard the result before using it.

javascriptjavascript
const modal = document.querySelector("#modal");
 
if (modal) {
  modal.classList.add("visible");
}

This guard pattern is essential. Without it, calling a method or accessing a property on null will crash your script with a TypeError. For most everyday DOM work, querySelector handles the vast majority of selection needs on its own.

querySelectorAll: Multiple Elements, Static List

When you need every element that matches a selector, querySelectorAll is the answer. It returns a static NodeList containing all matching elements in document order:

javascriptjavascript
const allParagraphs = document.querySelectorAll("p");
console.log(allParagraphs.length);
 
const activeItems = document.querySelectorAll(".item.active");
const formFields = document.querySelectorAll("input, select, textarea");

A NodeList is array-like: it has a length property, supports index access with square brackets, and has a built-in forEach method for iteration.

It does not have map, filter, or reduce, but you can easily convert it to a real array with Array.from or spread syntax when you need those methods.

javascriptjavascript
const links = document.querySelectorAll("a");
 
links.forEach((link) => {
  console.log(link.href);
});

The most important detail about querySelectorAll is that the returned NodeList is static. It is a snapshot taken at the exact moment you called the method.

If you later add a new paragraph or link to the page, the existing NodeList from an earlier querySelectorAll call will not include it. This is usually what you want because it avoids unexpected changes while you are iterating.

getElementsByClassName and getElementsByTagName: Live Collections

These two methods are older but have one unique feature: they return live HTMLCollections that automatically update when the DOM changes.

javascriptjavascript
const cards = document.getElementsByClassName("card");
const listItems = document.getElementsByTagName("li");

The live behavior is the key difference from querySelectorAll. Instead of freezing a snapshot, the collection stays connected to the live page and reflects every insertion or removal immediately:

javascriptjavascript
const items = document.getElementsByClassName("item");
console.log(items.length);  // Suppose it is 3
 
const newDiv = document.createElement("div");
newDiv.className = "item";
document.body.appendChild(newDiv);
 
console.log(items.length);  // Now 4

After the new element is appended, the collection size updates automatically. This live behavior is the defining feature of these two methods.

One limitation to remember: HTMLCollections do not have forEach. Always convert to an array before iterating:

javascriptjavascript
const cards = document.getElementsByClassName("card");
Array.from(cards).forEach((card) => {
  card.style.border = "1px solid blue";
});

Live collections are useful when you are adding and removing elements in the same script and want your reference to stay current. For most other cases, querySelectorAll with its static NodeList is simpler and less surprising.

Calling Selectors on Elements, Not Just the Document

Every selection method works on individual elements, not just the document object. This limits the search to that element's children, which is both cleaner and often faster:

htmlhtml
<section id="blog">
  <article class="post">First</article>
  <article class="post">Second</article>
</section>
<article class="post">Third (outside)</article>

The third article sits outside the blog section, which means scoped selection should ignore it. Call querySelectorAll on the blog section instead of the document to prove this:

javascriptjavascript
const blogSection = document.querySelector("#blog");
const blogPosts = blogSection.querySelectorAll(".post");
console.log(blogPosts.length);

The output is 2. The third article with class "post" sits outside the blog section, so the scoped querySelectorAll does not find it. Scoping like this is especially useful in component-based code and when working with sections of a page that have repeating class names.

Common Mistakes

The most frequent mistake with querySelector and querySelectorAll is forgetting the CSS prefix for classes and IDs. These methods use CSS selector syntax, so a class needs a dot and an ID needs a hash:

javascriptjavascript
// Wrong: looks for a <submit-btn> HTML tag, which likely does not exist
document.querySelector("submit-btn");  // null
 
// Correct
document.querySelector("#submit-btn");

A second common mistake is calling array methods like forEach directly on a live HTMLCollection instead of converting it first. And a third is forgetting to check for null after querySelector before accessing properties. If the element might not be on the page, the guard pattern shown earlier is not optional, it is required.

Where to Go Next

Now that you know all five selection methods, learn the two most important ones in depth: how to use getElementById for ID-based lookups and how to use querySelector and querySelectorAll for CSS-based selection. Once you can select elements, learn how to change text content on the elements you find.

Rune AI

Rune AI

Key Insights

  • Use getElementById when you have a unique ID on the element.
  • Use querySelector for flexible one-element lookups with CSS selectors.
  • Use querySelectorAll when you need every matching element.
  • getElementsByClassName and getElementsByTagName return live collections.
  • All selectors work on any element, not just the document.
RunePowered by Rune AI

Frequently Asked Questions

Which DOM selector is the fastest?

getElementById is the fastest because IDs are stored in a direct lookup table. querySelector is fast but must parse CSS selector syntax first. In most real applications the difference is too small to matter, so choose based on what makes your code clearer.

Does querySelectorAll return a live collection?

No. querySelectorAll returns a static NodeList, a snapshot of elements at the time you called it. getElementsByClassName and getElementsByTagName return live HTMLCollections that update automatically when the DOM changes.

Can I use querySelector on an element instead of document?

Yes. You can call querySelector on any element node. It will search only that element's descendants, not the entire document.

Conclusion

Selecting DOM elements is the first step in every interactive JavaScript feature. getElementById is the simplest and fastest for unique elements. querySelector is the most flexible and is the go-to for most situations. Knowing all five methods means you can pick the right tool for each task.