NodeList vs HTMLCollection: Understanding JavaScript DOM Collections

You select elements from the DOM and log the result
console.log(document.querySelectorAll("div"))
The browser returns something called a NodeList.
But if you run
document.getElementsByClassName("content")
you get something called an HTMLCollection.
Both look like arrays. Both contain DOM elements. But they behave differently.
Understanding this difference is critical when working with the DOM.
Understanding the DOM Collection Idea
When JavaScript selects multiple elements, the browser doesn't return a single element. It returns a collection -> a special object that holds multiple nodes together.
const items = document.querySelectorAll(".page__inner");
// items is a collection, not a single element
console.log(items)
// NodeList [div.page__inner]
Think of a collection like a container. It holds references to DOM nodes that matched your selector. You can access individual items, check how many exist, or loop through them.
The browser provides two main types of collections: NodeList and HTMLCollection. They serve similar purposes but have important differences under the hood.
What is NodeList
A NodeList is a collection of nodes returned by certain DOM methods. The most common source is querySelectorAll().
const cards = document.querySelectorAll(".card")
console.log(cards)
// NodeList(3) [div.card, div.card, div.card]
NodeList can contain different types of nodes
Element nodes (HTML elements like
div,p,span)Text nodes (the actual text content)
Comment nodes (HTML comments)
Most of the time, you will work with element nodes. But the key point is that NodeList is node-agnostic -> it can hold any type of node depending on how it was created.
Accessing elements works with bracket notation
const firstCard = cards[0]
const secondCard = cards[1]
What is HTMLCollection
An HTMLCollection is a collection containing only HTML elements. It cannot hold text nodes or comments just elements.
Common methods that return HTMLCollection:
// By class name
document.getElementsByClassName("btn")
// By tag name
document.getElementsByTagName("li")
// By name attribute
document.getElementsByName("email")
HTMLCollection represents a live view of the DOM. This is its defining characteristic. When the document changes, the collection updates automatically to reflect those changes.
Key Differences
Here's a clear comparison of the two collection types
| Feature | NodeList | HTMLCollection |
|---|---|---|
| Live vs Static | Usually static (doesn't auto-update) | Always live (auto-updates when DOM changes) |
| Node Types | Can contain elements, text nodes, comments | Contains only HTML elements |
| Common Methods | querySelectorAll(), childNodes |
getElementsByClassName(), getElementsByTagName(), children |
| forEach Support | Yes, built-in | No, must convert to array first |
| Item Access | Index brackets [0] |
Index brackets [0] or .item(0) method |
Static vs Live Collections
NodeList from querySelectorAll is static. Once created, it captures a snapshot of elements at that moment. Adding or removing elements later won't change your NodeList.
HTMLCollection is live. It stays connected to the DOM. If you add a matching element, it appears in your collection automatically.
Watch this difference in action
// Start with two items
const container = document.getElementById("list")
// Static NodeList
const staticItems = document.querySelectorAll(".item")
console.log(staticItems.length) // 2
// Live HTMLCollection
const liveItems = document.getElementsByClassName("item")
console.log(liveItems.length) // 2
// Add a new element
const newItem = document.createElement("div")
newItem.className = "item"
container.appendChild(newItem)
// Check again
console.log(staticItems.length) // Still 2 (static snapshot)
console.log(liveItems.length) // Now 3 (live update)
The static NodeList missed the new element. The live HTMLCollection caught it immediately.
When does this matter? If you're looping through a collection while modifying the DOM, live collections can behave unexpectedly because they update during iteration. Static collections provide predictable, stable references.
Iterating Through Them
Both collections support basic for loops
// Works for both
for (let i = 0; i < items.length; i++) {
console.log(items[i])
}
NodeList has built-in forEach:
const nodeList = document.querySelectorAll(".card")
nodeList.forEach((card, index) => {
console.log(`Card ${index}:`, card)
})
HTMLCollection lacks forEach directly. You must convert it first or use a traditional loop
const htmlCollection = document.getElementsByClassName("card")
// This won't work
// htmlCollection.forEach(card => console.log(card)) // Error
// These work
for (let card of htmlCollection) {
console.log(card)
}
Converting to an Array
Sometimes you need array methods like map(), filter(), or reduce(). Both collections can become true arrays
// Method 1: Array.from()
const array1 = Array.from(document.querySelectorAll(".item"))
// Method 2: Spread operator
const array2 = [...document.getElementsByClassName("item")]
// Now you can use any array method
const filtered = array1.filter(item => item.textContent.includes("Active"))
Why convert?
To use modern array methods
To create a stable copy you can modify safely
To work with frameworks that expect true arrays
Note that converting a live HTMLCollection to an array creates a static snapshot, breaking the live connection.
SUMMARY
NodeList
Returned by
querySelectorAll()andchildNodesUsually static (snapshot in time)
Can contain various node types
Supports
forEachdirectly
HTMLCollection
Returned by
getElementsByClassName(),getElementsByTagName(),getElementsByName()Always live (updates with DOM changes)
Contains only HTML elements
Requires conversion for array methods
Both represent groups of DOM elements, but understanding how they update and behave helps avoid many mistakes. Choose querySelectorAll when you want a stable list to work with. Use getElementsByClassName when you need a dynamic view that tracks changes automatically.




