JavaScriptBeginner

JavaScript DOM Selectors: getElementById, querySelector and More

Learn to select elements with getElementById, getElementsByClassName, getElementsByTagName, querySelector and querySelectorAll, and when to use each one.

All JavaScript lessons

What you will learn

Before JavaScript can change anything on a page, it must first find the element. This is called selecting an element. In this lesson you will learn the five main ways to select elements: getElementById, getElementsByClassName, getElementsByTagName, querySelector and querySelectorAll. You will learn what each one returns, how live and static collections are different, and which method to choose in real projects.

Why do we need to select elements?

Imagine you are in a big school and the principal wants to talk to someone. There are three ways to call them:

  • By their roll number (unique to one student). This is like an id.
  • By their class (many students share it). This is like a class.
  • By their type (all students, all teachers). This is like a tag name.

JavaScript does the same with the page. The selecting methods give you a reference to the element, which you store in a variable. After that, you can change it, read it, or react to it.

const title = document.getElementById("page-title");   // find it
title.textContent = "Big Sale!";                         // use it

The sample page

All examples in this lesson use this small shop page. It is also loaded in the Practice in Editor button, so you can try every line.

<h1 id="page-title">MyCodeNest Store</h1>

<ul id="menu">
  <li><a href="#home" class="link active">Home</a></li>
  <li><a href="#courses" class="link">Courses</a></li>
  <li><a href="#contact" class="link">Contact</a></li>
</ul>

<div class="product-list">
  <div class="product featured">
    <h2>Pen</h2>
    <p class="price">₹10</p>
  </div>
  <div class="product">
    <h2>Notebook</h2>
    <p class="price">₹60</p>
  </div>
  <div class="product sold-out">
    <h2>Bag</h2>
    <p class="price">₹800</p>
  </div>
</div>

<form id="signup">
  <input type="text" name="username" placeholder="Your name">
  <input type="email" name="email" placeholder="Your email">
  <button type="submit" class="btn">Join</button>
</form>

Some CSS makes the products look like cards, so you can see your changes clearly:

.product-list {
  display: flex;
  gap: 12px;
  margin: 16px 0;
}

.product {
  border: 1px solid #ccc;
  border-radius: 8px;
  padding: 8px 16px;
}

.price {
  font-weight: bold;
}

1. getElementById

Finds the one element with the given id. It returns the element, or null if nothing matches.

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

console.log(title);               // <h1 id="page-title">MyCodeNest Store</h1>
console.log(title.textContent);   // "MyCodeNest Store"

console.log(document.getElementById("missing"));   // null

Things to remember:

  • Write the id without the # sign. getElementById("#page-title") returns null.
  • It works only on document. You cannot write someDiv.getElementById(...).
  • An id must be unique. If two elements share an id (a mistake), you get the first one.
  • Capital letters matter: getElementById (not getElementByID), and "Page-Title" is not "page-title".

Real use: the “Pay now” button, a modal, a form, a main container. Anything that exists once on a page.

2. getElementsByClassName

Finds all elements with a class. It returns an HTMLCollection, a list-like object (never null; an empty collection if nothing matches).

const products = document.getElementsByClassName("product");

console.log(products.length);              // 3
console.log(products[0].textContent);      // text of the Pen card
console.log(products[2]);                  // the Bag card

Write the class without the dot: "product", not ".product".

You can ask for elements that have several classes at once by separating them with spaces:

const featured = document.getElementsByClassName("product featured");
console.log(featured.length);   // 1 (only the Pen has both classes)

An HTMLCollection is not an array. You can use length, an index like products[0], and for...of, but not forEach, map or filter:

for (const product of products) {
  console.log(product.querySelector("h2").textContent);   // Pen, Notebook, Bag
}

// products.forEach(...)   // TypeError: products.forEach is not a function

Array.from(products).forEach(function (p) {   // convert to a real array first
  console.log(p.className);
});

A common mistake is treating the collection as one element:

document.getElementsByClassName("price").textContent;   // undefined (a collection has no textContent)
document.getElementsByClassName("price")[0].textContent; // "₹10" (pick one item first)

3. getElementsByTagName

Finds all elements with a tag name. It also returns a live HTMLCollection.

const items = document.getElementsByTagName("li");
console.log(items.length);   // 3

const inputs = document.getElementsByTagName("input");
console.log(inputs.length);  // 2

const all = document.getElementsByTagName("*");   // every element on the page
console.log(all.length);

Tag names are not case sensitive in HTML ("li" and "LI" both work). Use it for general jobs, like counting all images or links:

console.log("Images on page:", document.getElementsByTagName("img").length);
console.log("Links on page:", document.getElementsByTagName("a").length);

Bonus: other old-style helpers

Helper Returns
document.getElementsByName("email") All elements with name="email" (useful for radio buttons)
document.forms All <form> elements
document.images All <img> elements
document.links All <a> and <area> elements with an href

4. querySelector

This is the most flexible method. You give it any CSS selector, exactly as you would write in a CSS file, and it returns the first matching element (or null).

document.querySelector("#page-title");     // by id (use the # sign)
document.querySelector(".product");        // by class (use the dot): the Pen card
document.querySelector("h2");              // by tag: the first <h2>

Because it uses CSS selectors, you can be very specific:

Selector What it finds on the sample page
"#menu" The <ul> with id menu
"ul li a" The first link inside a list
"a.active" The Home link (it has the class active)
".product.featured .price" The price inside the featured product (₹10)
".product:not(.sold-out)" The first product that is not sold out (Pen)
".product:last-child" The Bag card (the last child of its parent)
"input[name='email']" The email input
"form button" The Join button

Selectors are covered fully in the CSS lessons CSS Syntax and Selectors and CSS Selectors in Depth. Everything you learned there works here.

const emailInput = document.querySelector("input[name='email']");
emailInput.placeholder = "you@example.com";

const joinButton = document.querySelector("form button");
joinButton.textContent = "Join Free";

If nothing matches, querySelector returns null. Calling a property on null crashes your script, so for elements that may not exist, check first or use optional chaining:

const banner = document.querySelector(".banner");

if (banner) {
  banner.textContent = "Sale!";
}

// or, shorter:
const text = document.querySelector(".banner")?.textContent ?? "No banner";
console.log(text);   // "No banner"

Tip: If your selector is not valid CSS (for example "..product"), querySelector throws a SyntaxError. If an id starts with a number or has special characters, write it with CSS.escape("1st-item"), or use getElementById, which accepts any id text.

5. querySelectorAll

The same as querySelector, but it returns all matches in a NodeList (an empty NodeList if there are none, never null).

const prices = document.querySelectorAll(".price");
console.log(prices.length);   // 3

prices.forEach(function (price) {
  console.log(price.textContent);   // ₹10, ₹60, ₹800
});

A NodeList is a bit friendlier than an HTMLCollection:

You can use on a NodeList You cannot use
length, [index], item(i) map, filter, reduce, find
forEach push, pop
for...of, entries()

To use array methods, convert it first with Array.from() or the spread syntax:

const prices = document.querySelectorAll(".price");

const numbers = Array.from(prices).map(function (p) {
  return Number(p.textContent.replace("₹", ""));
});

console.log(numbers);   // [10, 60, 800]

const expensive = [...prices].filter(function (p) {
  return Number(p.textContent.replace("₹", "")) > 50;
});
console.log(expensive.length);   // 2

Searching inside an element

All the methods above, except getElementById, also work on any element, not only on document. The search then happens only inside that element. This is called scoping, and it is how you handle repeated blocks like product cards.

document.querySelectorAll(".product").forEach(function (card) {
  const name = card.querySelector("h2").textContent;
  const price = card.querySelector(".price").textContent;
  console.log(name + " costs " + price);
});

Output:

Pen costs ₹10
Notebook costs ₹60
Bag costs ₹800

If you had searched with document.querySelector(".price") inside the loop, every card would give you the Pen’s price. Searching inside card fixes that.

Live vs static collections

This is a favourite interview question, so let us make it clear.

  • A live collection updates itself automatically when the page changes. (getElementsByClassName, getElementsByTagName)
  • A static collection is a snapshot taken at the moment you call the method. It does not change later. (querySelectorAll)

You can see the difference with this experiment:

const liveList = document.getElementsByClassName("product");
const staticList = document.querySelectorAll(".product");

console.log(liveList.length, staticList.length);   // 3 3

// Remove all classes from the Bag card (it is no longer a "product")
document.querySelector(".sold-out").className = "";

console.log(liveList.length, staticList.length);   // 2 3

The live list noticed the change and now has 2 items. The static list still remembers the old 3.

Neither is better. Live lists are surprising when you loop and change the page at the same time (you will see a bug caused by this in the traversal lesson). Static lists are easier to reason about, which is one reason many developers prefer querySelectorAll.

Good to know: Not every NodeList is static. The childNodes property returns a live NodeList. Only the one from querySelectorAll is a snapshot.

Comparing the five methods

Method Finds Returns If nothing matches Live?
getElementById("x") One element by id Element null n/a
getElementsByClassName("x") All with a class HTMLCollection Empty collection Yes
getElementsByTagName("x") All with a tag HTMLCollection Empty collection Yes
querySelector("css") The first match of any selector Element null n/a
querySelectorAll("css") All matches of any selector NodeList Empty NodeList No

Which one should I use?

  • Need one element by id? Use getElementById("id") (it is simple and very fast), or querySelector("#id"). Both are fine.
  • Need one element by anything else? Use querySelector.
  • Need many elements? Use querySelectorAll.
  • Reading old code or answering an interview question? Know all five, because the older ones are still everywhere.

In modern projects, querySelector and querySelectorAll handle almost everything. The speed difference between the methods is too small to matter for normal pages.

Real use cases

Calculate a cart total

const prices = document.querySelectorAll(".price");
let total = 0;

prices.forEach(function (price) {
  total += Number(price.textContent.replace("₹", ""));
});

console.log("Total:", total);   // Total: 870

Mark sold-out products and feature one

const soldOut = document.querySelector(".sold-out");
soldOut.style.opacity = "0.4";

document.querySelector(".product.featured").style.border = "2px solid gold";
const links = document.querySelectorAll("#menu .link");

links.forEach(function (link) {
  link.style.color = "gray";
});

document.querySelector("#menu .active").style.color = "green";

A complete working page

This page counts the products that are in stock, shows the total price, and fades the sold-out product. It is a full page, so press Try it to run it.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Stock Summary</title>
  <style>
    body { font-family: sans-serif; padding: 20px; }
    .product-list { display: flex; gap: 12px; margin-bottom: 16px; }
    .product { border: 1px solid #ccc; border-radius: 8px; padding: 8px 16px; }
    .price { font-weight: bold; }
    .summary { padding: 12px; background: #eff6ff; border-radius: 8px; }
  </style>
</head>
<body>
  <div class="product-list">
    <div class="product">
      <h2>Pen</h2>
      <p class="price">₹10</p>
    </div>
    <div class="product">
      <h2>Notebook</h2>
      <p class="price">₹60</p>
    </div>
    <div class="product sold-out">
      <h2>Bag</h2>
      <p class="price">₹800</p>
    </div>
  </div>

  <p class="summary" id="summary"></p>

  <script>
    const cards = document.querySelectorAll(".product");
    let inStock = 0;
    let total = 0;

    cards.forEach(function (card) {
      if (card.matches(".sold-out")) {
        card.style.opacity = "0.4";
        return;   // skip sold-out products
      }

      inStock++;
      const priceText = card.querySelector(".price").textContent;
      total += Number(priceText.replace("₹", ""));
    });

    document.getElementById("summary").textContent =
      inStock + " products in stock. Total price: ₹" + total;
  </script>
</body>
</html>

Here card.matches(".sold-out") asks “does this element match this selector?” and gives true or false. It is a handy helper that you will meet again in the traversal lesson.

Common mistakes

  • Using # or . with the old methods. getElementById("#title") and getElementsByClassName(".product") return nothing. Only querySelector and querySelectorAll use # and ..
  • Forgetting that a collection is not one element. getElementsByClassName("price").textContent is undefined. Use an index, or loop.
  • Calling forEach on an HTMLCollection. It has no forEach. Use for...of, or Array.from() first.
  • Using querySelector when you want all. It gives only the first match.
  • Not handling null. If the element does not exist, querySelector and getElementById give null. Check it, or use ?..
  • Running the script before the element exists. Put the script at the end of <body>, or use defer (see the DOM introduction lesson).
  • Searching the whole page inside a loop. Use card.querySelector(...) to search inside the current card.
  • Duplicate ids. getElementById returns only the first one. Use classes for repeated things.
  • Expecting querySelectorAll to update itself. It is a snapshot. Select again after you add or remove elements.

Practice

  1. Select the page title with getElementById and change its text with textContent.
  2. Use getElementsByClassName("product") to print how many products there are, then print the name (h2) of each one with for...of.
  3. Use getElementsByTagName("a") to count the links, and getElementsByTagName("input") to count the inputs.
  4. Use querySelector to select the email input and change its placeholder.
  5. Use querySelectorAll(".price") and forEach to add a "+ GST" text at the end of every price. (Hint: price.textContent += " + GST".)
  6. Use .product:not(.sold-out) with querySelectorAll to count the products in stock.
  7. Try document.querySelector(".banner") and read the result. Then write code that changes the banner text only if the banner exists.
  8. Create liveList and staticList as in the live vs static experiment, but this time give the first menu item the class product with document.querySelector("li").className = "product". Which list grows from 3 to 4, and which one stays at 3?
  9. Challenge: with querySelectorAll, Array.from and map, build an array of objects like { name: "Pen", price: 10 } from the three product cards, and print it with console.table.

Recap

  • Selecting means finding an element so you can change or read it. Every selecting method starts from document (or another element).
  • getElementById("id") returns one element or null. No # sign.
  • getElementsByClassName("x") and getElementsByTagName("x") return a live HTMLCollection. It has length and indexes, but no forEach or map.
  • querySelector("css") returns the first match or null. querySelectorAll("css") returns a static NodeList of all matches. Both accept any CSS selector.
  • A NodeList has forEach. To use map or filter, convert it with Array.from() or [...list].
  • Call the methods on an element (for example card.querySelector(".price")) to search only inside it.
  • A live collection changes with the page. A static one is a snapshot.
  • For most work, use querySelector and querySelectorAll. Use getElementById for ids. Always handle the null case.
  • Next you will learn DOM traversal: moving from one element to its parent, children and siblings.