DOM selectors in JavaScript
DOM selectors are the ways to find elements on a page with JavaScript. Without them you cannot interact with HTML at all: you first have to get the element in order to do something with it (change its text or style, remove it, attach an event handler and so on).
Theory
TL;DR
- A selector is a way to say which element in the DOM you want: by
id, tag, class, attribute or a full CSS selector. getElementByIdreturns a single element, whilegetElementsByClassNameandgetElementsByTagNamereturn an HTMLCollection.querySelectorreturns the first match for a CSS selector,querySelectorAllreturns all matches as a NodeList.- The
getElementsBy...collections are live: a new element in the DOM shows up in them automatically. The NodeList fromquerySelectorAllis static. - You can narrow the search down: a found element also has
querySelectorandquerySelectorAll.
Quick example
// HTML: <ul class="menu"><li>Home</li><li class="active">Profile</li></ul>
const menu = document.querySelector('.menu'); // the first match
const items = menu.querySelectorAll('li'); // search inside menu
const active = document.querySelector('.menu li.active');
console.log(items.length); // 2
console.log(active.textContent); // ProfileThe main element lookup methods
| Method | What it returns | What it takes | Example |
|---|---|---|---|
getElementById(id) | a single element | id (without #) | document.getElementById("header") |
getElementsByClassName(class) | HTMLCollection (array like) | class name (without .) | document.getElementsByClassName("menu") |
getElementsByTagName(tag) | HTMLCollection | tag name | document.getElementsByTagName("p") |
querySelector(selector) | the first element | CSS selector | document.querySelector(".menu li") |
querySelectorAll(selector) | NodeList (every match) | CSS selector | document.querySelectorAll("div > p") |
Each method in detail
document.getElementById() finds one element by its id attribute.
<h1 id="title">Hello!</h1>const el = document.getElementById('title');
console.log(el.textContent); // Hello!Pro: very fast. Cons: works by id only and returns a single element.
document.getElementsByClassName() looks for all elements with the given class.
<p class="text">First</p>
<p class="text">Second</p>const els = document.getElementsByClassName('text');
console.log(els.length); // 2Pro: fast and supported everywhere. Con: it returns an HTMLCollection, which you have to convert to an array (Array.from) before using forEach.
document.getElementsByTagName() finds every element by tag.
const paragraphs = document.getElementsByTagName('p');It returns a live collection: if a new <p> appears in the DOM, it is added to the collection automatically.
document.querySelector() uses CSS selectors and returns the first matching element.
const item = document.querySelector('.menu li.active');Pro: universal, it supports everything CSS selectors can express. Con: it returns only one element.
document.querySelectorAll() returns every element that matches the CSS selector.
const items = document.querySelectorAll('.menu li');
items.forEach((li) => console.log(li.textContent));Pro: it returns a NodeList, which already has forEach(). Con: the collection does not update itself, unlike getElementsBy....
Types of selectors (as in CSS)
| Selector | Example | Description |
|---|---|---|
| By tag | "div" | every <div> |
| By class | ".item" | every element with the class item |
By id | "#header" | the element with id="header" |
| By attribute | "[type='text']" | elements with type="text" |
| Descendants | "ul li" | every <li> inside a <ul> |
| Direct children | "ul > li" | only the ones one level deep |
| Combined | "div.item.active" | <div class="item active"> |
| Pseudo classes | "li:first-child", "input:checked" | specific states |
When to use what
| Goal | Better to use |
|---|---|
Get an element by id quickly | getElementById() |
| Find by class or tag | getElementsByClassName() / getElementsByTagName() |
| A complex lookup (as in CSS) | querySelector() / querySelectorAll() |
| A collection that updates itself | getElementsBy... |
| A one off static list | querySelectorAll() |
An example of a combined lookup. HTML:
<div class="card">
<h2 class="title">Title</h2>
<p class="desc">Description</p>
</div>JavaScript:
const card = document.querySelector('.card');
const title = card.querySelector('.title');
const desc = card.querySelector('.desc');
console.log(title.textContent); // "Title"A practical tip: reach for querySelector / querySelectorAll by default, they are the universal and modern way and work with any CSS selector. getElementById and getElementsByClassName are useful where you need maximum performance (legacy code or thousands of elements).
Working with the elements you found
Once the element is found you can do almost anything with it:
const el = document.querySelector('#myDiv');
el.textContent = 'New text'; // change the content
el.style.color = 'red'; // change the style
el.classList.add('active'); // add a class
el.addEventListener('click', onClick); // attach a handlerA short recap of the return values:
| Method | Returns | Notes |
|---|---|---|
getElementById(id) | a single element | fast, unique id |
getElementsByClassName() | HTMLCollection | a live collection |
getElementsByTagName() | HTMLCollection | lookup by tag |
querySelector() | the first element | CSS selector |
querySelectorAll() | NodeList | every matching element |
Common mistakes
- Passing
#or.where they are not expected.getElementById('#title')returnsnull: this method takes a bareid.querySelector('#title'), on the other hand, requires the#. - Calling
forEachon an HTMLCollection.getElementsByClassName/getElementsByTagNamehave noforEach; useArray.from(els)first, or switch toquerySelectorAll. - Not checking the result for
null.querySelectorandgetElementByIdreturnnullwhen nothing is found, and touching.textContentthen throws immediately. - Forgetting that collections are live. A loop over an HTMLCollection that removes elements inside the body skips some of them, because the collection changes as you go.
- Looking for elements before the HTML is parsed. A script in
<head>withoutdeferruns before the markup exists, so the selector finds nothing. - Always searching from
document. If you already hold a container, callcard.querySelector('.title'): it is both faster and more precise.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.