DOM Manipulation
Reaching Into the Living Tree
LinkedIn Hook
You can write flawless JavaScript logic and still freeze the moment the interviewer says: "Render these 1,000 items without locking the browser."
That's not an algorithm question. That's a DOM question.
The DOM is the browser's live in-memory tree of your HTML. Every query, every append, every class toggle either runs cheaply or forces the browser to rebuild layout — and the difference between a fast UI and a janky one usually comes down to understanding that tree.
In this lesson you'll learn how to select, create, and modify DOM nodes; why
textContentbeatsinnerText; and why oneDocumentFragmentcan be ten times faster than a naive loop.If you've ever wondered why appending 1,000
<li>s kills your page — this lesson fixes that.Read the full lesson -> [link]
#JavaScript #DOM #Frontend #WebPerformance #InterviewPrep #CodingInterview #WebDevelopment
What You'll Learn
- How to select, create, append, and remove DOM nodes with modern APIs
- The real differences between
innerHTML,textContent, andinnerText(including XSS and reflow implications) - Why
DocumentFragmentslashes reflow cost for batch inserts
The Living Tree
The DOM (Document Object Model) is the browser's in-memory representation of your HTML. Think of it as a living tree — every element is a node, and JavaScript can reach into that tree to read, add, remove, or modify any node at any time.
Selecting Elements
// By ID — returns single element or null
const header = document.getElementById("main-header");
// querySelector — returns FIRST match (CSS selector)
const firstBtn = document.querySelector(".btn");
const nav = document.querySelector("nav > ul");
// querySelectorAll — returns ALL matches (NodeList, NOT live)
const allButtons = document.querySelectorAll(".btn");
// Older methods (still valid, return live HTMLCollections)
const divs = document.getElementsByTagName("div");
const items = document.getElementsByClassName("item");
Key difference: querySelectorAll returns a static NodeList (a snapshot). getElementsByClassName and getElementsByTagName return live HTMLCollections that automatically update when the DOM changes.
Creating, Appending, and Removing Elements
// Create
const card = document.createElement("div");
card.className = "card";
card.textContent = "New Card";
// Append
document.getElementById("container").appendChild(card);
// Modern append (accepts strings too)
container.append(card, " some text");
// Insert before a specific element
const ref = document.querySelector(".reference");
container.insertBefore(card, ref);
// Remove
card.remove(); // Modern
// OR
card.parentNode.removeChild(card); // Older approach
Modifying Attributes, Classes, and Styles
const el = document.querySelector(".box");
// Attributes
el.setAttribute("data-id", "42");
el.getAttribute("data-id"); // "42"
el.removeAttribute("data-id");
// Classes
el.classList.add("active", "highlighted");
el.classList.remove("highlighted");
el.classList.toggle("active"); // add if absent, remove if present
el.classList.contains("active"); // true or false
// Inline styles
el.style.backgroundColor = "coral";
el.style.transform = "translateX(100px)";
innerHTML vs textContent vs innerText
const div = document.querySelector("#example");
// innerHTML — parses HTML (XSS risk if used with user input!)
div.innerHTML = "<strong>Bold</strong> text";
// textContent — raw text, ignores HTML, includes hidden elements
div.textContent = "<strong>Bold</strong> text";
// Renders literally: "<strong>Bold</strong> text"
// innerText — "visible" text only, triggers reflow to compute
console.log(div.innerText); // only what's visually rendered
Performance note: innerText is slower because it triggers a reflow to compute CSS visibility. Prefer textContent when you don't need visual awareness.
DocumentFragment for Batch Operations
This is a critical performance pattern. Every time you append to the live DOM, the browser may trigger a reflow/repaint. A DocumentFragment lets you build a subtree off-screen, then insert it all at once.
// BAD — triggers reflow on every iteration
function addItemsSlow(count) {
const list = document.getElementById("list");
for (let i = 0; i < count; i++) {
const li = document.createElement("li");
li.textContent = `Item ${i}`;
list.appendChild(li); // reflow each time!
}
}
// GOOD — single reflow with DocumentFragment
function addItemsFast(count) {
const list = document.getElementById("list");
const fragment = document.createDocumentFragment();
for (let i = 0; i < count; i++) {
const li = document.createElement("li");
li.textContent = `Item ${i}`;
fragment.appendChild(li); // off-screen, no reflow
}
list.appendChild(fragment); // ONE reflow
}
// Performance test
console.time("slow");
addItemsSlow(1000); // ~50ms+ depending on complexity
console.timeEnd("slow");
console.time("fast");
addItemsFast(1000); // ~5-10ms
console.timeEnd("fast");
Reflow vs Repaint
- Reflow (Layout): The browser recalculates positions and dimensions of elements. Triggered by changes to width, height, margin, padding, adding/removing elements, reading
offsetHeight, etc. - Repaint: The browser redraws pixels (color, shadow, visibility changes) without recalculating layout. Cheaper than reflow.
Every reflow triggers a repaint, but not every repaint triggers a reflow.
Common Mistakes
- Using
innerHTMLwith untrusted strings — any user-supplied value becomes executable markup and opens an XSS hole. UsetextContentfor text or sanitize before injecting HTML. - Appending nodes inside a loop to the live DOM — each append can trigger a reflow. Build inside a
DocumentFragmentand insert once. - Assuming
querySelectorAllreturns a live collection — it returns a static snapshot, so DOM changes after the call won't show up. Live collections come fromgetElementsByClassName/getElementsByTagName.
Interview Questions
Q: What is the difference between querySelector and getElementById?
getElementByIdonly selects by ID and returns a single element.querySelectoraccepts any CSS selector and returns the first match.getElementByIdis slightly faster for ID lookups, butquerySelectoris more versatile.
Q: Why use DocumentFragment?
DocumentFragment is an off-screen container that doesn't trigger reflow when you append to it. You build your subtree inside the fragment, then insert it into the live DOM in one operation — causing only a single reflow instead of one per element.
Q: What is the difference between innerHTML and textContent?
innerHTMLparses and renders HTML markup (potential XSS risk with user input).textContenttreats everything as raw text, is faster, and is safe from injection.innerTextis similar totextContentbut only returns visible text and triggers reflow.
Q: What is the DOM? How does JavaScript interact with it?
The DOM is a tree-structured, in-memory representation of the parsed HTML document that the browser builds. JavaScript interacts with it through the
documentobject and its APIs (querySelector,createElement,appendChild, etc.), reading and mutating nodes to change what the user sees.
Q: What is the difference between querySelector and querySelectorAll?
querySelectorreturns the first matching element (ornull).querySelectorAllreturns a static NodeList of every match; it's iterable withforEachbut is not live.
Q: Why is innerText slower than textContent?
innerTextneeds to know what's visually rendered, so it forces the browser to flush pending layout work (a reflow) before returning.textContentreads the raw node text without consulting styles, so it's cheap.
Q: What triggers a reflow? What triggers only a repaint?
Reflow is triggered by anything that changes geometry: adding/removing nodes, changing width/height/margin/padding, changing fonts, or reading layout properties like
offsetHeight. Repaint-only changes don't affect layout — color,visibility,box-shadow, orbackground-imageswaps.
Quick Reference — Cheat Sheet
DOM MANIPULATION — QUICK MAP
Selection:
getElementById("id") -> single element or null
querySelector(".css") -> first match (CSS selector)
querySelectorAll(".css") -> static NodeList
getElementsByClassName(...) -> LIVE HTMLCollection
Create / Insert / Remove:
createElement("div")
parent.appendChild(node) | parent.append(...nodes, ...strings)
parent.insertBefore(node, refNode)
node.remove()
Attributes / Classes / Styles:
el.setAttribute / getAttribute / removeAttribute
el.classList.add / remove / toggle / contains
el.style.<camelCaseProp> = "..."
Text vs HTML:
textContent -> raw text, fast, safe
innerText -> visible text, triggers reflow
innerHTML -> parses HTML, XSS risk with user input
Batch inserts:
const f = document.createDocumentFragment();
// append many nodes to f (no reflow)
parent.appendChild(f); // ONE reflow
Previous: Error Handling in Promises Next: Event Handling & Event Delegation
This is Lesson 11.1 of the JavaScript Interview Prep Course — 14 chapters, 87 lessons.