Skip to content
JavaScript js dom 4 min read

Selecting Elements

Before you can read text, change styles, or attach event listeners, you have to find the right element in the page. The DOM gives you two families of methods for this: the older getElementById / getElementsByClassName style, and the modern, far more flexible querySelector / querySelectorAll style that accepts any CSS selector. This page explains how each works, the crucial difference between live and static collections, and how to turn the results into real arrays you can map and filter over.

The getElement* family

These methods predate CSS-selector queries. Each one looks up elements by a single, specific criterion, and their names tell you exactly what they match.

const header = document.getElementById("main-header");          // one element or null
const items  = document.getElementsByClassName("list-item");    // HTMLCollection
const inputs = document.getElementsByTagName("input");          // HTMLCollection

getElementById is the fastest lookup in the DOM and returns a single element (or null if nothing matches). The plural methods return an HTMLCollection — an array-like object that is live, meaning it updates automatically as the document changes. Note that getElementById only exists on document, while the others can be called on any element to scope the search to that subtree.

Element IDs must be unique on a page. If two elements share an ID, getElementById returns the first one and your code becomes unpredictable — prefer classes or attributes for groups.

querySelector and querySelectorAll

querySelector and querySelectorAll accept any CSS selector string, so you can express complex queries that the getElement* methods simply cannot. querySelector returns the first match (or null); querySelectorAll returns all matches as a NodeList.

const firstError = document.querySelector(".form .error");      // first match or null
const allLinks   = document.querySelectorAll("nav a[href^='/']"); // every match
const row        = table.querySelector("tr:nth-child(2) td");   // scoped to `table`

Because they take full selectors, these two methods cover descendant combinators, attribute selectors, pseudo-classes, and grouping — all in a single readable call.

// Grab every checked checkbox inside the settings panel
const checked = document.querySelectorAll("#settings input[type='checkbox']:checked");
console.log(checked.length);

Output:

3

Live vs static collections

This is the difference that trips people up. getElementsByClassName and getElementsByTagName return a live HTMLCollection that reflects the DOM in real time. querySelectorAll returns a static NodeList — a snapshot taken at the moment you called it that never changes afterward.

const live   = document.getElementsByClassName("box"); // live HTMLCollection
const frozen = document.querySelectorAll(".box");      // static NodeList

document.body.append(document.createElement("div")).className = "box";

console.log(live.length);   // increased — it saw the new element
console.log(frozen.length); // unchanged — it's a snapshot

Output:

4
3

The live behavior is a classic source of infinite loops: iterating a live collection while adding matching elements inside the loop keeps growing it forever. Static NodeLists avoid that, which is one reason querySelectorAll is the safer default.

MethodReturnsSelector?Live or staticOn any element?
getElementByIdElement | nullID onlyn/aNo (document only)
getElementsByClassNameHTMLCollectionclass onlyLiveYes
getElementsByTagNameHTMLCollectiontag onlyLiveYes
querySelectorElement | nullAny CSSn/aYes
querySelectorAllNodeListAny CSSStaticYes

Converting NodeLists to arrays

A NodeList has forEach, but it lacks map, filter, reduce, and the rest of the array toolkit. An HTMLCollection doesn’t even have forEach. To use array methods, convert the collection first with Array.from() or the spread operator.

const nodes = document.querySelectorAll("li");

const texts = Array.from(nodes, (li) => li.textContent.trim()); // map while converting
const spread = [...document.getElementsByClassName("tag")];     // HTMLCollection → array

const active = [...nodes].filter((li) => li.classList.contains("active"));
console.log(texts, active.length);

Array.from is especially handy because its optional second argument is a mapping function, so you convert and transform in one pass.

A runnable demo

The pen below selects elements both ways and shows the live/static difference when you add a new item.

<ul id="list">
  <li class="item">Apples</li>
  <li class="item">Bananas</li>
  <li class="item">Cherries</li>
</ul>
<button id="add">Add item</button>
<p id="out"></p>

<script>
  const live   = document.getElementsByClassName("item"); // live
  const frozen = document.querySelectorAll(".item");      // static snapshot
  const out    = document.querySelector("#out");

  function render() {
    out.textContent = `live = ${live.length}, static = ${frozen.length}`;
  }

  document.querySelector("#add").addEventListener("click", () => {
    const li = document.createElement("li");
    li.className = "item";
    li.textContent = `Item ${live.length + 1}`;
    document.querySelector("#list").append(li);
    render(); // live grows, static stays at 3
  });

  render();
</script>

Best Practices

  • Reach for querySelector / querySelectorAll by default — one consistent, selector-based API covers nearly every case.
  • Use getElementById when you have a known unique ID and want the fastest possible lookup.
  • Always handle the null return from querySelector and getElementById before touching properties.
  • Convert collections with Array.from() or [...collection] before using map, filter, or reduce.
  • Remember querySelectorAll is static and getElementsBy* is live; don’t mutate the DOM while iterating a live collection.
  • Scope queries to a container element (container.querySelectorAll(...)) instead of always searching from document for clearer, faster code.
  • Quote attribute values inside selectors (input[type='text']) to stay valid across all selector engines.
Last updated June 1, 2026
Was this helpful?