The DOM
The live, in-memory version of a page's structure that a browser builds and that JavaScript can read and change.
What is it?
The HTML you write is just text in a file — it's the starting instructions for a page, not the page itself while it's running. The moment a browser loads that HTML, it reads through it and builds a completely separate, living representation of the page in memory: a tree of objects, one for each element, each one knowing its content, attributes, and which elements are nested inside which.
That in-memory tree is called the DOM — the Document Object Model. It's what the browser actually uses to draw the page on screen, and crucially, it's what JavaScript reaches into when it wants to change something after the page has loaded: add a new item to a list, change a heading's text, remove an element entirely.
This distinction matters: editing the original HTML file does nothing to a page that's already open in a browser. But changing the DOM does — because the DOM, not the original file, is the thing currently being rendered. Open a browser's developer tools and inspect an element, and you're looking at the live DOM, which may no longer match the HTML that was first sent down.
Explain like I'm 10
The original HTML file is like a printed recipe. The DOM is the actual dish being cooked in the kitchen right now — you can still add a pinch of salt or swap an ingredient mid-cook, and none of that touches the printed recipe sitting on the counter.
Examples
HTML source vs. the live DOM
<!-- original HTML file -->
<ul id="list">
<li>Apples</li>
</ul>
// JavaScript, run after the page loads
document.getElementById("list").innerHTML += "<li>Bananas</li>";The HTML file only ever had one list item. After this script runs, the live DOM has two — but if you viewed the file's raw source again, it would still only show one, because the file itself never changed.
Reading and changing an element
const heading = document.querySelector("h1");
console.log(heading.textContent); // reads the current text
heading.textContent = "New Title"; // changes it livequerySelector finds a node in the DOM tree. From there, JavaScript can both read its current state and assign new values, and the visible page updates right away.
How it works
As the browser parses HTML, for every tag it encounters it creates a corresponding node object and attaches it to a tree, nested according to how the tags were nested in the source. Each node has properties (its tag name, attributes, text, children) and methods JavaScript can call to read or change them. Because the DOM is just a set of objects in memory, any change to it — adding a node, deleting one, changing text — is reflected on screen essentially immediately, without needing to reload anything.
HTML source (text)
|
| browser parses it
v
DOM tree (live objects in memory)
|
|<--- JavaScript reads/writes here
v
Rendered page on screenWhy does it exist?
The DOM exists because pages needed a way to change after they finish loading, without the browser having to re-fetch and re-parse the whole HTML file from scratch for every small update. Representing the page as a tree of live, addressable objects gives JavaScript a structured, efficient way to inspect and modify exactly the parts of a page that need to change.
When to use it
Any time JavaScript needs to read what's currently on a page or change it — show a new element, hide something, update text after fetching data — it does so through the DOM, not by editing HTML files.
When not to use it
For content that never needs to change after the page loads, there's no need to manipulate the DOM with JavaScript at all — plain HTML and CSS are simpler and faster. Heavy, frequent DOM manipulation for large, fast-changing interfaces is also often better handled through a framework that manages DOM updates efficiently, rather than doing it all by hand.
Common mistakes
Confusing 'view page source' (the original HTML sent by the server) with the live DOM shown in developer tools inspector — they can differ after scripts run.
Assuming editing the HTML file will change a page that's already open in a browser tab.
Repeatedly querying the DOM for the same element inside a loop instead of looking it up once and reusing the reference.
Practice exercises
- Easy:
Use document.querySelector to select an element on a page and log its text content to the console.
- Medium:
Write a script that adds a new paragraph element to the page after it has already loaded, then explain why 'view page source' wouldn't show it.
- Hard:
Explain, in your own words, the difference between the DOM and HTML, and describe one situation where confusing the two would cause a debugging mistake.
Interview questions
What is the DOM?
The Document Object Model — a live, in-memory tree of objects representing a page's current structure, built by the browser from the HTML and used both for rendering and for JavaScript to read or change the page.
How is the DOM different from the original HTML source?
The HTML source is static text describing the page's starting state. The DOM is a live, in-memory representation built from that text, and it can be changed after the page loads through JavaScript without the underlying HTML file itself ever changing.
Why can JavaScript change what's on screen without reloading the page?
Because it modifies the live DOM tree directly — adding, removing, or editing node objects in memory — and the browser re-renders based on the current state of that tree, not by re-reading the original HTML file.
What's the difference between `textContent` and `innerHTML`, and why does it matter for security?
textContent treats whatever you assign to it as plain text, escaping any markup so it displays literally. innerHTML parses the assigned string as HTML and creates real elements from it — which means inserting untrusted user input through innerHTML can let attacker-supplied <script> or event-handler markup run, a class of bug known as XSS.
What's the difference between `document.getElementById`, `document.querySelector`, and `document.querySelectorAll`?
getElementById looks up a single element by its id directly, without evaluating a CSS selector, and is the fastest of the three. querySelector accepts any CSS selector and returns the first matching element. querySelectorAll also accepts a CSS selector but returns all matches, as a NodeList, rather than just the first.
Is the NodeList returned by `querySelectorAll` live or static? What about `getElementsByClassName`?
querySelectorAll returns a static snapshot — if elements matching that selector are added to the DOM afterward, the NodeList you already have won't include them. getElementsByClassName (and getElementsByTagName) return a live HTMLCollection that automatically reflects later additions or removals matching the query.
What's the difference between a DOM 'node' and an 'element'?
Node is the general category — it includes element nodes, but also text nodes (the actual text between tags) and comment nodes. An element is specifically a node created from an HTML tag, like a <div> or <p>; not every node in the tree is an element.
How would you move from a child element to its parent, and to its next sibling, in the DOM?
element.parentElement (or parentNode) gives the containing element. element.nextElementSibling and previousElementSibling move across to adjacent elements at the same level, skipping over any text or comment nodes in between.
What's the difference between `element.children` and `element.childNodes`?
children returns only the child elements, skipping text and comment nodes. childNodes returns every child node, including whitespace-only text nodes created by the line breaks and indentation in your source HTML — which is why childNodes.length is often surprisingly larger than expected.
How do you create a brand-new element and add it to the page with JavaScript?
document.createElement("li") creates a detached element that isn't part of the page yet, then parent.appendChild(newElement) (or insertBefore for a specific position) attaches it into the live DOM tree, at which point the browser renders it.
What actually happens when you set `element.innerHTML = ""`?
The browser destroys every existing descendant node of that element — including any event listeners attached directly to them — and replaces them with nothing, since an empty string parses to no content at all.
What is event bubbling?
When an event fires on an element, it doesn't just run handlers on that exact element — it then propagates upward, triggering matching handlers on each ancestor in turn, all the way up to the document, unless something stops it along the way.
What is event delegation, and why does bubbling make it possible?
Instead of attaching a separate click handler to every item in a list, you attach one handler to their shared parent and inspect event.target inside it to figure out which item was actually clicked. This works because a click on any descendant bubbles up and also triggers the parent's handler.
Given `<div id="outer"><button id="btn">Click</button></div>` with click listeners on both `#outer` and `#btn`, and no `stopPropagation` anywhere, what order do the handlers run in when the button is clicked?
The #btn handler runs first, then the #outer handler — bubbling-phase handlers fire from the actual target outward to its ancestors, not the other way around.
What's the difference between `event.stopPropagation()` and `event.preventDefault()`?
stopPropagation() stops the event from continuing to bubble up (or capture down) to other handlers, but doesn't affect the browser's default behavior for that event. preventDefault() cancels the browser's default action, like following a link or submitting a form, but doesn't stop the event from still reaching other handlers.
Why does repeatedly calling `document.querySelector` for the same element inside a loop hurt performance?
Each call makes the browser search through (part of) the DOM tree again to find a match. If the element doesn't change between iterations, looking it up once outside the loop and reusing that reference avoids paying that search cost on every single iteration.
What's the difference between `appendChild` and `append`?
appendChild accepts exactly one Node and returns that node. append (newer) can accept multiple arguments, including plain strings as well as nodes, and returns undefined — it's more flexible but not usable everywhere appendChild's return value is needed.
A `<script>` at the top of `<head>` runs `document.querySelector("#footer")` and gets `null`, even though `<div id="footer">` clearly exists further down in the `<body>`. Why?
The browser parses HTML top to bottom, building the DOM incrementally, and a script placed in <head> executes before the parser has even reached the <body> — so the #footer element simply doesn't exist in the DOM yet at the moment the script runs.
How does adding `defer` to a `<script>` tag relate to that problem?
defer tells the browser to keep parsing the HTML and building the DOM without waiting for the script, then run the script only after parsing is complete — so by the time it executes, every element defined in the HTML is already present in the DOM.
What is a `DocumentFragment`, and why use one before inserting many nodes into the DOM?
It's a lightweight container that lives outside the visible DOM tree — you can build a batch of nodes inside it first, then append the whole fragment in a single operation. That means the browser only has to update the live page once, instead of once per node.
You need to render 100 new list items from fetched data — why is calling `list.appendChild(item)` 100 times in a loop, directly on the live list, less efficient than building the items off-DOM first and inserting them all at once?
Each appendChild call on an element that's already part of the rendered page can trigger the browser to recompute layout and repaint, since the visible page just changed. Building the nodes in memory (or in a DocumentFragment) first and inserting them in one call means the browser only has to do that layout/paint work once instead of up to 100 times.
Why is toggling a CSS class with `classList.add`/`classList.remove` often preferred over setting `element.style` properties directly?
classList keeps the actual visual values in CSS, where they belong and can be reused, and lets you flip a whole group of related style changes on or off with one call. Setting element.style directly hardcodes specific property values into JavaScript, one at a time, which is harder to keep consistent and mixes styling concerns into your logic.