Skip to main content

Command Palette

Search for a command to run...

NodeList vs HTMLCollection: Understanding JavaScript DOM Collections

Updated
5 min read
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() and childNodes

  • Usually static (snapshot in time)

  • Can contain various node types

  • Supports forEach directly

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.

More from this blog