Selecting Elements

Learn the different ways to select DOM elements from JavaScript — by id, class, tag name, and CSS selector — and understand when to use each.

Before you can change anything on a page, you first have to find it. The DOM provides several selection methods, each with different trade-offs between simplicity, flexibility, and the kind of result they return.

getElementById

document.getElementById(id) returns the single element whose id attribute matches exactly, or null if no such element exists. Because HTML ids are supposed to be unique on a page, this method returns one element directly — not a list — making it the fastest and simplest selector when you know an element's id.

Note that you pass the id without the # prefix (unlike CSS), since this method is specifically about the id attribute, not a general selector language.

getElementsByClassName and getElementsByTagName

document.getElementsByClassName(className) returns all elements that have the given class, and document.getElementsByTagName(tagName) returns all elements with the given tag name (like all <li> elements). Both return a live HTMLCollection — 'live' meaning it automatically updates if matching elements are added or removed from the page later.

An HTMLCollection looks array-like (it has a .length and can be indexed with [0], [1], etc.) but it is not a real array — it lacks methods like .map() or .forEach() directly, so it's often converted to a real array with Array.from() before using array methods on it.

querySelector and querySelectorAll

document.querySelector(cssSelector) returns the first element matching any valid CSS selector — not just id or class, but combinators, attribute selectors, pseudo-classes, anything CSS supports. It's the most flexible and generally the most commonly used selection method in modern code.

document.querySelectorAll(cssSelector) returns all matching elements as a NodeList. Unlike an HTMLCollection, a NodeList from querySelectorAll is static (a snapshot at the time of the call, not automatically updated) but it does support .forEach() directly, which makes it convenient to loop over.

• document.querySelector("#main") — id selector, same target as getElementById("main") • document.querySelector(".card") — first element with class "card" • document.querySelectorAll("li") — every <li> element on the page • document.querySelectorAll(".card.active") — every element with both classes "card" and "active" • document.querySelector("ul > li:first-child") — any valid CSS selector works, including combinators

Choosing the right method

In modern code, querySelector / querySelectorAll are usually preferred by default because they accept any CSS selector, which developers already know from styling and which scales to complex cases without learning a separate API. getElementById remains a fine, slightly faster choice when you specifically have a unique id to target.

The older getElementsBy... methods are less commonly written in new code today, but you will still encounter them in existing codebases, and it's important to remember they return live collections, which behaves differently from the static NodeList that querySelectorAll gives you.

The five core selection methods

getElementById does not use a '#' prefix; querySelector does, matching CSS syntax. querySelector and querySelectorAll return null / an empty NodeList (not an error) when nothing matches. querySelectorAll's NodeList supports .forEach() directly; HTMLCollections from getElementsBy... do not.

document.getElementById("header");
document.getElementsByClassName("item");   // live HTMLCollection
document.getElementsByTagName("li");        // live HTMLCollection
document.querySelector(".item");            // first match, or null
document.querySelectorAll(".item");         // static NodeList, all matches

Code Examples

Selects a single unique element by id, and separately selects the first element matching a class using the more general querySelector. Expected result: The text content of the element with id="page-title", followed by the first <element class="card"> found in the document.

document.getElementById("page-title") returns exactly one element (or null), never a list.

querySelectorAll returns every matching element as a NodeList, which supports forEach directly, making it straightforward to loop over and process each match. Expected result: 3 (or however many .card elements exist) Card 0: ... Card 1: ... Card 2: ...

cards.forEach((card, index) => ...) iterates the NodeList just like an array, giving both the element and its position.

Demonstrates the 'live' behavior of an HTMLCollection: after a new matching element is added to the page, the previously stored liveItems collection automatically reflects the new count, without calling getElementsByClassName again. Expected result: 2 3 liveItems.length updates automatically because HTMLCollection is a live view into the document, not a frozen snapshot.

Common mistake: Relying on this 'live' behavior unintentionally can cause confusing bugs (like an off-by-one loop) if you don't realize the collection is changing size while you iterate it — querySelectorAll's static NodeList avoids this surprise.

const title = document.getElementById("page-title");
console.log(title.textContent);

const firstCard = document.querySelector(".card");
console.log(firstCard);
const cards = document.querySelectorAll(".card");
console.log(cards.length);

cards.forEach((card, index) => {
  console.log(`Card ${index}:`, card.textContent.trim());
});
const liveItems = document.getElementsByClassName("item");
console.log(liveItems.length); // e.g. 2

const newItem = document.createElement("li");
newItem.className = "item";
document.querySelector("ul").appendChild(newItem);

console.log(liveItems.length); // automatically 3 now — no re-query needed

Important Rules

getElementById expects a bare id (no '#'); querySelector/querySelectorAll expect a full CSS selector (with '#' or '.' as appropriate).

querySelector and getElementById return null when nothing matches — always check for null before using the result to avoid a TypeError.

getElementsByClassName/TagName return live collections that auto-update; querySelectorAll returns a static NodeList captured at call time.

querySelectorAll's NodeList has .forEach(); HTMLCollections from getElementsBy... do not — convert with Array.from() first if you need array methods.

Where this shows up

Nearly every interactive feature starts with selecting an element: grabbing a form to attach a submit handler, selecting all checkboxes to compute a total, finding a modal to open or close it, or selecting every navigation link to highlight the current page. querySelector/querySelectorAll are the everyday workhorses of DOM scripting.

const el = document.getElementById("missing"); el.textContent = "Hi"; // TypeError: el is null const el = document.getElementById("missing"); if (el) { el.textContent = "Hi"; }

Selection methods return null (not an error) when no element matches, so code that immediately accesses a property on the result without checking for null will throw a TypeError. Always guard with an if check, especially for elements that may not always exist.

document.getElementsByClassName("item").forEach(el => ...); // TypeError

Array.from(document.getElementsByClassName("item")).forEach(el => ...);

HTMLCollection (returned by getElementsByClassName/TagName) does not have a forEach method, unlike a real array or a querySelectorAll NodeList. Convert it with Array.from() first, or use querySelectorAll instead, which supports forEach natively.

[Multiple Choice] What does document.querySelector(".card") return if there are three elements with class "card" on the page? A. An array of all three elements B. The first matching element only C. A NodeList of all three elements D. null, because it's ambiguous

B. The first matching element only — querySelector always returns only the first element that matches the given selector, even if multiple elements match. Use querySelectorAll to get all of them.

[True / False] A NodeList returned by querySelectorAll automatically updates if matching elements are later added to the page. A. True B. False

B. False — False. querySelectorAll returns a static NodeList — a snapshot taken at the moment it was called. It will not include elements added afterward, unlike the live HTMLCollection returned by getElementsByClassName/TagName.

Given a page with several <li class="todo-item"> elements inside a <ul id="todo-list">, write JavaScript that selects all of them and logs the text content of each one to the console.

const items = document.querySelectorAll(".todo-item"); items.forEach(item => { console.log(item.textContent.trim()); });

Use document.querySelectorAll(".todo-item") and then .forEach().