DOM API — Complete Course Notes
Frontend Masters · Frontend System Design · DOM API
Start by changing a heading with JavaScript, then learn to find elements, add content, and build cards from a template.
On your first read, follow the explanations and small examples. Each “Course details” panel preserves the instructor's explanations, questions, performance discussion, and clearly labeled technical corrections. Each transcript panel retains every original English entry, including repetitions and transcription errors. The examples in the main text were added or simplified while preparing these notes; they are not verbatim copies of the instructor's code.
Timestamps come from the supplied file, not converted video times. These notes cover that file rather than claiming to reproduce the entire recorded course. The original bilingual transcript remains available as a download.
Download the complete original bilingual transcript · Back to the course index
Why use the DOM API? Start by changing a heading
The DOM is the tree structure a browser uses to represent a web page. Headings, buttons, and input fields have corresponding DOM elements. The DOM API gives JavaScript the tools to find, change, add, and remove those elements.
Suppose a page contains this heading:
<h1 id="title">My portfolio</h1>
You can change its text with JavaScript:
const title = document.getElementById('title'); // Find the heading
title.textContent = 'Welcome to my website'; // Change its text
That is a DOM operation: find an element, then change its content.
Who updates the DOM when you use React or Vue?
Usually, the framework does. You update your data, and the framework reflects that change on the page. You do not necessarily write the code above yourself.
The instructor describes three situations where you may need to take responsibility for those updates:
| Situation | Why might you need to manipulate the DOM yourself? |
|---|---|
| Building a low-level library similar to React | A virtual DOM is data that describes the UI. After comparing the old and new versions, the library still needs the DOM API to update the actual page. |
| Building a player or chart component that works across frameworks | The user supplies a container, and your component creates and updates its content. This lets it work without depending on a particular framework. |
| Building a portfolio with only a few interactions | You can use the DOM API directly for something like expanding an introduction when a button is clicked, reducing the libraries needed for a small interaction. A purely static page may only need HTML and CSS. |
We will work through finding elements, changing them, and then building reusable cards.
Read more: course details, questions, and technical corrections
Source timestamps: 14:37:27–14:38:30
The DOM API is a set of methods for manipulating the DOM. React, Angular, and Vue usually handle DOM changes for us, so application developers do not always call these methods directly. The instructor lists several situations where understanding them is useful:
- Building low-level libraries that work with a virtual DOM or manage the DOM.
- Creating reusable components that can move between frameworks, such as video players and chart engines.
- Building a personal portfolio while keeping the application lightweight by importing fewer libraries.
This section is a quick refresher. It begins with the available objects, then compares ways to query and modify the DOM.
Complete original English transcript (14:37:27–14:38:30)
[14:37:27] [EN] manipulate your DOM. So what is the DOM API?
[14:37:30] [EN] So the DOM API is a set of methods that we can
[14:37:32] [EN] can utilize to manipulate a DOM.
[14:37:35] [EN] And usually we don't use it directly
[14:37:38] [EN] because most of the libraries are handling the DOM
[14:37:42] [EN] mutations for us, for instance, React, Angular, or Vue.
[14:37:45] [EN] but there are cases where we actually
[14:37:48] [EN] we actually need to use the DOM API.
[14:37:51] [EN] One of that is when you're building some low-level
[14:37:54] [EN] of a library that works with virtual
[14:37:57] [EN] or some DOM management. Also,
[14:38:00] [EN] Also, it's used when you need to create some generic components
[14:38:03] [EN] components that can be ported to any library or framework.
[14:38:06] [EN] can be a video player or a chart engine.
[14:38:09] [EN] And also, for instance, you are building your
[14:38:13] [EN] your portfolio website and you want to minimize
[14:38:15] [EN] the blueprint of your application
[14:38:18] [EN] by not importing too many libraries. So this way
[14:38:21] [EN] you can just use the DOM API and your app will be
[14:38:24] [EN] be lightweight. So this is actually
[14:38:27] [EN] This section will give us a quick refresher on how to use
[14:38:30] [EN] use the DOM API.
Where do you start looking? Meet document
document represents the current web document. It is the usual starting point for finding elements on a page. The browser provides it, so you do not need an import.
document.body; // This document's <body>
document.head; // This document's <head>
document.getElementById('title'); // The element with ID title
The instructor also introduces window, which you can think of as an entry point for working with the current browser window. Here, window.document and document refer to the same document.
What is the difference between an element and a node?
Consider this small piece of HTML:
<p>Hello</p>
The browser represents both the p element and its text:
p element
└─ "Hello" text node
Node is the broader category; an Element is one kind of node. The p is both an element and a node. Its text is a text node, but not an element.
The instructor also introduces classes such as HTMLElement and Text. On a first read, focus on distinguishing documents, elements, and text nodes. The full inheritance overview is below.
Read more: course details, questions, and technical corrections
Source timestamps: 14:38:33–14:41:03
window is globally accessible in the browser and requires no import. document represents the current document and is accessible through either window.document or document. document.body and document.head provide quick access to the body and head.
The instructor introduces these types in order:
- HTMLElement represents HTML elements on an HTML page.
- Element is where he places shared element operations in his explanation, because the DOM also handles SVG and XML elements.
- Node provides a common interface for representing nodes and relationships in the DOM tree.
- Text represents text in the DOM. Text nodes inherit from Node, not HTMLElement.
- Document is described as an exception: it inherits from Node but also provides DOM query methods. The instructor explicitly says he does not know the reason for this design.
- Window holds a reference to the current document. He describes it in terms of the browser viewport and does not explore its prototype properties here.
Editorial clarification: The simplified hierarchy is shown below. Document is not the HTML root element itself. The instructor's informal statement that “HTMLElement has no DOM API” should not be read as saying it has no properties or methods of its own. Shared APIs exist on interfaces including Node and Element; Document and Element also receive common operations through mixins.
EventTarget
└─ Node
├─ Document
├─ DocumentFragment
├─ Element
│ ├─ HTMLElement
│ └─ SVGElement
└─ CharacterData
└─ Text
See the WHATWG DOM Standard for the interface and mixin definitions.
Complete original English transcript (14:38:33–14:41:03)
[14:38:33] [EN] So first, let's overview the objects
[14:38:36] [EN] that are available to us and how can we use
[14:38:39] [EN] we used to don't want BI. So the first close
[14:38:42] [EN] global object that we are all aware of is the
[14:38:45] [EN] window. The window is a globally
[14:38:48] [EN] accessible objects, so you don't need to import anything.
[14:38:51] [EN] And the next one is the document.
[14:38:54] [EN] The document represents HTML element, basically the base
[14:38:57] [EN] page that we are currently rendering.
[14:39:00] [EN] The document can be accessed through the window
[14:39:03] [EN] or directly by referencing
[14:39:06] [EN] in this as a global object.
[14:39:09] [EN] Then we have some quick access links
[14:39:12] [EN] that we can use on the document. It's a body and a head.
[14:39:15] [EN] gives you us the body tech and head respectively.
[14:39:18] [EN] Now let's go for
[14:39:21] [EN] through the class hierarchy.
[14:39:24] [EN] All the elements on the HTML page are
[14:39:27] [EN] are represented by HTML element.
[14:39:30] [EN] apparently the HTML element doesn't have any DOM API,
[14:39:33] [EN] assigned to that. The DOM API is actually assigned
[14:39:36] [EN] assigned to the element product
[14:39:39] [EN] prototype. So why is that? Because the
[14:39:42] [EN] the Dome API can actually work not with
[14:39:45] [EN] with just HTML, so it can be also SVG elements or
[14:39:48] [EN] or XML-like things. That's why the
[14:39:51] [EN] The DOM API lives on the element prototype instead of HTML
[14:39:54] [EN] element. And we also have
[14:39:57] [EN] have the note the dome is a tree
[14:40:00] [EN] and somehow we need to access the tree properties
[14:40:03] [EN] So that's why we have a special class that designed
[14:40:06] [EN] to represent a tree leaf and this is the node.
[14:40:09] [EN] and we have exception which is
[14:40:13] [EN] is a text node which basically extends the node class the text node
[14:40:15] [EN] Xnode is used when we use
[14:40:18] [EN] any text, that when we need to wrap our text
[14:40:22] [EN] text in the HTML element. So the browser needs to somehow
[14:40:24] [EN] how we represent the text that we place in our
[14:40:27] [EN] in our tags. Then we have the
[14:40:30] [EN] HTML document. An HTML document is an outlier here because
[14:40:33] [EN] because it has the DOM API assigned to its
[14:40:36] [EN] its prototype, but it extends the node.
[14:40:39] [EN] I don't know the reason why, but this is the exception in the whole
[14:40:42] [EN] and the whole hierarchy that we need to live with.
[14:40:45] [EN] And the next one is the window.
[14:40:49] [EN] Basically, the window has an instance of first HTML document, and it doesn't
[14:40:51] [EN] It doesn't have any interesting properties on its product.
[14:40:54] [EN] So it represents the browser viewport. Query method.
[14:40:57] [EN] methods. Let's now understand how the browser
[14:41:00] [EN] queries elements on a page.
[14:41:03] [EN] page, how the DOM API queries the elements.
How do you find an element? Start with querySelector
Before changing an element, you need to find it. querySelector uses the same familiar matching patterns you use in CSS. A matching condition like this is called a selector.
<section id="cards">
<h3 class="card-title">First card</h3>
<h3 class="card-title">Second card</h3>
</section>
document.querySelector('#cards'); // # selects by ID
document.querySelector('.card-title'); // . selects by class; first match only
document.querySelector('h3'); // Select by tag name; first match only
To find every card title, use querySelectorAll:
const titles = document.querySelectorAll('.card-title');
// Change every matching title
titles.forEach((title) => {
title.textContent = 'New title';
});
When there is no match, querySelector returns null, while querySelectorAll returns an empty list.
What other query methods does the instructor cover?
| Method | What does it match? | One result or several? |
|---|---|---|
getElementById('cards') | An ID, without # | One element, or null |
getElementsByClassName('card-title') | A class, without . | Multiple elements |
getElementsByTagName('h3') | A tag name | Multiple elements |
querySelector('.card-title') | A CSS selector | The first match, or null |
querySelectorAll('.card-title') | A CSS selector | All matching elements |
For now, focus on what each method does. The instructor's discussion of speed, memory, and browser lookup strategies is below. Next, we will look at a difference that directly affects the results your code sees.
Read more: course details, questions, and technical corrections
Source timestamps: 14:41:06–14:45:22
This section preserves the instructor's cost model for comparing APIs. The complexity and caching descriptions below are classroom models, not performance guarantees that every browser must follow.
| Method | How the instructor describes query cost | Result and main point |
|---|---|---|
getElementById(id) | A hash map maps IDs to element references; lookup is described as O(1), like accessing a value by key | One element; low read cost |
getElementsByClassName(className) | Tree traversal, O(n) in the worst case; repeated queries may benefit from caching | A live HTMLCollection that reflects DOM changes |
getElementsByTagName(tagName) | The same general model as class lookup, with a tag condition instead | A live HTMLCollection |
querySelector(selector) | CSS selector processing; complexity depends on the selector; explained using DFS and ID optimizations | The first matching element, without collection maintenance |
querySelectorAll(selector) | Selector processing, described as DFS plus a hash map | A static NodeList whose membership does not update automatically |
getElementById
The instructor says that as the browser reads a page, it builds a cache mapping IDs to element references. Looking up an ID is therefore compared to accessing an object by key. The transcript's “0.1” is interpreted as O(1) from context, not 0.1 milliseconds.
Live HTMLCollection
For a class query, he describes walking the tree to find elements with the class text, potentially checking all elements. He adds that caching can make an identical later query much faster.
HTMLCollection is an older collection interface with a live property: removing an element from the DOM changes the collection. He argues that maintaining this behavior requires checking positions and existence again, so he also describes a read as O(n). He presents lower memory overhead as an advantage and suggests considering this collection when memory is limited. His explanation for tag queries is the same.
querySelector and querySelectorAll
The instructor emphasizes selector parsing or compilation costs: more complex selectors may cost more to process. He describes class and tag matching using DFS, while an ID selector may be optimized into an ID lookup.
querySelector returns one element. For querySelectorAll, he describes paying additional memory cost at query time in exchange for direct indexed access afterward. He warns against querying excessively large sets of elements.
Technical correction: The matching membership list is static, not the contents of each node. querySelectorAll() does not clone every DOM element; its results still refer to the original nodes. Storing the result list takes space, but that does not mean it copies the whole DOM. A live HTMLCollection also does not imply that every read must traverse the entire tree again. The standard defines behavior, not these Big-O bounds or caching strategies. See DOM collections and MDN querySelectorAll.
Complete original English transcript (14:41:06–14:45:22)
[14:41:06] [EN] So let's start with the first, the most simple method, which is
[14:41:09] [EN] is getElementById. So when the browser
[14:41:12] [EN] reads the page, it constructs the hash model
[14:41:15] [EN] of the elements, so it reached the ID
[14:41:18] [EN] and then it basically builds the cache where the IDE
[14:41:21] [EN] is assigned to some HTML reference.
[14:41:24] [EN] So this means that when we try to
[14:41:27] [EN] to get the element by the time complexity and
[14:41:30] [EN] and read cost is just 0.1.
[14:41:33] [EN] because this is just basically accessing the object
[14:41:36] [EN] by its key.
[14:41:39] [EN] The situation is slightly different when it comes to
[14:41:42] [EN] get elements by class name.
[14:41:45] [EN] So to get the elements by the class name,
[14:41:48] [EN] we need to actually traverse the whole tree.
[14:41:52] [EN] And the way the browser traverses a tree, it's used to
[14:41:55] [EN] approach. So it basically goes for the elements
[14:41:57] [EN] and finds all the
[14:42:00] [EN] elements on a page with a class text.
[14:42:04] [EN] So, and the time complexity for this is
[14:42:07] [EN] is linear, so we need to go through all elements.
[14:42:10] [EN] elements in the worst case, but you see there is a star
[14:42:12] [EN] are here. This is because the browser is quite
[14:42:16] [EN] quite smart and it utilizes the hash map when you
[14:42:19] [EN] when you try to query things. So the first question
[14:42:22] [EN] query will take some time but when if you're
[14:42:24] [EN] execute this same query next time the browser
[14:42:28] [EN] it will be almost immediately because browser utilizes
[14:42:31] [EN] that utilizes the caching mechanism.
[14:42:33] [EN] One thing about this method is also
[14:42:36] [EN] so it returns the HTML collection.
[14:42:39] [EN] HTML collection is the special, actually, legacy collection
[14:42:42] [EN] collection which
[14:42:45] [EN] has one interesting characteristic, it's life.
[14:42:48] [EN] So this means that if we modify the DOM tree
[14:42:51] [EN] for instance remove one element from the DOM,
[14:42:54] [EN] This will be reflected on our HTML collection
[14:42:58] [EN] collection. So, and that's why the read code
[14:43:00] [EN] cost of the HTML collection is
[14:43:03] [EN] big O of N because every time when
[14:43:06] [EN] we need to read a single element, the browser
[14:43:09] [EN] will barst the tree again to verify
[14:43:12] [EN] verify its position.
[14:43:15] [EN] This is a disadvantage of this thing.
[14:43:19] [EN] On the other hand, the memory cost is low
[14:43:22] [EN] because the browser doesn't need to clone any objects.
[14:43:25] [EN] It just takes the already existing reference
[14:43:27] [EN] references. So you can utilize the HTML
[14:43:30] [EN] collection when you
[14:43:34] [EN] need to be low, like when you're low on memory, you need to
[14:43:37] [EN] you need to use as less memory as possible.
[14:43:40] [EN] Okay, same is
[14:43:43] [EN] It's the same way works the getElementByTagName.
[14:43:46] [EN] So there is no difference at all. It's just like it allows
[14:43:49] [EN] allows us to query things by the tech.
[14:43:52] [EN] So the query selector works
[14:43:55] [EN] slightly different. So it utilizes
[14:43:58] [EN] the CSS selector, so the time complexity depends
[14:44:01] [EN] depends on the selection that we use.
[14:44:04] [EN] always pay some tax
[14:44:07] [EN] on compiling the selector.
[14:44:10] [EN] So this means that the more complex selection
[14:44:13] [EN] the more time it takes to compile it for you.
[14:44:16] [EN] for a CSS.
[14:44:19] [EN] So the read access and the memory cost is
[14:44:22] [EN] because we're just returning the single
[14:44:25] [EN] the single element and the querying mechanism
[14:44:28] [EN] when we try to use the class names or the text
[14:44:31] [EN] it's the same as the DFS if we use the ID
[14:44:34] [EN] then we're going to convert the statement to get element
[14:44:37] [EN] and by ID instead.
[14:44:40] [EN] So one interesting thing about the query selection
[14:44:43] [EN] and this is the query selector O is because
[14:44:46] [EN] because it returns a nod list. The nod list is a
[14:44:49] [EN] another type of collection, but it's not life. It's just basically
[14:44:52] [EN] the copy of the elements. So what the browser does
[14:44:55] [EN] does, it's just copies the HTML
[14:44:58] [EN] HTML objects, and this means that we pay additional
[14:45:01] [EN] memory cost when we do the querying, so it's
[14:45:04] [EN] it's not advisable to query too many things because
[14:45:07] [EN] because you may end up having the memory spike.
[14:45:10] [EN] But the read access is 0.1 because you just
[14:45:13] [EN] accessing the object directly from the array instead of
[14:45:16] [EN] traversing the tree again by maintaining the life
[14:45:19] [EN] life collection. And the querying mechanism is the same.
[14:45:22] [EN] DFS Plus HashMap.
What is a live collection?
A collection is simply a list of nodes or elements. A live collection automatically updates its membership when the relevant DOM changes. You keep the same collection, but the elements inside it can change.
We will build this idea step by step: automatic updates → static results → element references → loop pitfalls → choosing an API → performance.
Suppose the page has these cards:
<section id="cards">
<p class="card">A</p>
<p class="card">B</p>
</section>
const container = document.getElementById('cards');
const cards = container.getElementsByClassName('card');
console.log(cards.length); // 2
cards[0].remove();
console.log(cards.length); // 1: no need to query again
Think of cards as the matching elements inside this container right now. It does not watch every element everywhere: its search area is container, and its matching condition is the class card.
What makes the list change?
Adding a matching element, removing one, or changing whether it matches can update the collection. Continuing the example:
const newCard = document.createElement('p');
newCard.className = 'card';
newCard.textContent = 'C';
container.appendChild(newCard);
console.log(cards.length); // 2: B and C
newCard.classList.remove('card');
console.log(cards.length); // 1: only B matches now
C is still inside the container, but no longer matches .card. An element can leave a live collection without being removed from the page. Moving it outside the collection's search area can also remove it from the results.
const cards does not freeze the list. It only prevents assigning a different value to the variable. Also, “live” does not run your own callback whenever something changes; it describes the results you see when you access the collection.
Why do both live and static collections exist?
Programs can need two different things: the current members or the members found at the start of an operation.
Use a live collection when you want the list to stay up to date. For example, after adding a card, you may want an existing list to include it automatically. The browser keeps membership current, but the list's length and indexes can change while your code works with it.
Use a static list when you want to process a fixed batch. For example, find expired cards and remove those matches without the result list shrinking as you go. If you later need newly matching cards, query again.
The choice depends on whether your task needs an updated list or a stable batch. The next sections demonstrate the difference, list the APIs for each behavior, and explain the effects on loops and performance.
This explains the practical uses of the two behaviors, not their exact design history. The instructor speculates about why older APIs use live collections and later selector APIs offer static results; he does not provide a formal historical explanation.
Does the result list update when the page changes?
Suppose you find two cards and then delete one. Does the list you already have shrink to one card? It depends on the API.
getElementsByClassNameandgetElementsByTagNamereturn a live collection that updates with the DOM.querySelectorAllreturns a static NodeList whose membership does not update automatically.
This example creates a container with two cards and compares the results:
const container = document.createElement('div');
container.innerHTML = '<p class="card">A</p><p class="card">B</p>';
const liveCards = container.getElementsByClassName('card');
const staticCards = container.querySelectorAll('.card');
container.firstElementChild.remove(); // Remove card A
console.log(liveCards.length); // 1: automatically reflects the removal
console.log(staticCards.length); // 2: still lists the two original matches
A static list keeps the same members, but those elements can still change. Editing an element through the list changes the original element on the page.
const host = document.createElement('div');
host.innerHTML = '<p class="card">A</p>';
const snapshot = host.querySelectorAll('.card');
console.log(snapshot[0] === host.firstElementChild); // true: the same element
snapshot[0].textContent = 'Updated';
console.log(host.firstElementChild.textContent); // Updated
If a new card is added later, run another query when you need an up-to-date static result.
Which APIs are live, and which are static?
The API defines the behavior. Selecting by class does not automatically mean live: querySelectorAll('.card') still returns a static list.
| API or property | What it contains | Behavior |
|---|---|---|
getElementsByClassName() | Matching elements within the query area | Live HTMLCollection |
getElementsByTagName() | Elements with a matching tag name | Live HTMLCollection |
document.getElementsByName() | Elements with a matching name attribute | Live NodeList |
element.children | Direct child elements | Live HTMLCollection |
node.childNodes | Direct child nodes, including text and comments | Live NodeList |
document.forms / document.images | Forms / image elements in the document | Live HTMLCollection |
querySelectorAll() | Elements matching a selector at query time | Static NodeList |
Array.from(collection) / [...collection] | References copied into an ordinary array | Membership does not follow DOM changes |
HTMLCollection is live; NodeList can be live or static, depending on where it comes from. Arrays can be changed by your code, but they do not automatically track the DOM. querySelector() and getElementById() return one element or null, so they are not part of this collection comparison. See MDN NodeList, HTMLCollection, and getElementsByName.
How does children differ from childNodes?
children lists only direct child elements. childNodes also includes direct child text nodes, comments, and other node types.
Consider this HTML:
<p>Hello <strong>world</strong></p>
Its DOM structure looks like this:
p
├── Text node: "Hello "
└── strong element
└── Text node: "world"
The p has two direct child nodes: the text "Hello " and the strong element.
// Assuming this is the first <p> on the page
const p = document.querySelector('p');
console.log(p.children.length); // 1: only <strong>
console.log(p.childNodes.length); // 2: "Hello " and <strong>
"world" is not a separate entry in p.childNodes. It is a child of strong, not a direct child of p.
Line breaks and indentation between HTML tags can also become text nodes. That is why childNodes may contain nodes that look empty when you inspect them.
Both lists are live. The difference is which kinds of direct child nodes they include.
The common bug: removing elements while moving forward
Start with three cards, A, B, and C. This loop looks as if it should remove all of them:
const container = document.createElement('div');
container.innerHTML = '<p class="card">A</p><p class="card">B</p><p class="card">C</p>';
const cards = container.getElementsByClassName('card');
for (let i = 0; i < cards.length; i++) {
cards[i].remove();
}
// B remains!
| Step | What happens? | Live list afterward |
|---|---|---|
i = 0 | Remove A | [B, C] |
i = 1 | Index 1 now points to C, so remove C | [B] |
i = 2 | The length is now 1, so the loop stops | [B] |
The next element moves into the index you just visited, while your loop moves forward. The same problem can happen if you remove the matching class instead of removing the element.
Caching the original length does not freeze membership; later indexes may even become undefined. Switching to for...of does not turn a live collection into a snapshot either.
Safer ways to process the original matches
If you want to process exactly the cards that match now, use a static query:
const cards = container.querySelectorAll('.card');
cards.forEach((card) => card.remove());
If you already have a live collection, copy its references into an array before changing the DOM:
const liveCards = container.getElementsByClassName('card');
const cardsToRemove = Array.from(liveCards);
cardsToRemove.forEach((card) => card.remove());
The array's membership stays stable during removal. This copies the list of references, not the elements themselves.
For the specific job of removing every current match, you can also deliberately use the live behavior:
const cards = container.getElementsByClassName('card');
while (cards.length > 0) {
cards[0].remove(); // The next remaining card becomes index 0
}
This works because each iteration removes a member from the collection. A loop that only changes text would not shrink the collection and would not terminate.
Choose based on the behavior you need
| Your task | Which approach should you use? |
|---|---|
| Process a fixed batch of matching elements | Use querySelectorAll() (static), not a live collection whose members may change during the operation. You can also copy a live collection with Array.from(). |
| Keep an automatically updated list of direct child elements | Use children (live), not querySelectorAll(':scope > *') (static). Both select direct child elements, but only children updates automatically. |
| Include new matching cards after adding content | Use getElementsByClassName('card') (live), not an old querySelectorAll('.card') result (static). If you use the static result, run the query again to include new cards. |
| Find just one element | Use querySelector() or getElementById() (one element or null), not querySelectorAll() (a static list). Live versus static describes collections, not these single-element results. |
A static list can still hold an element after it leaves the page. If connection to a document matters to your operation, check element.isConnected; that is separate from whether it still matches a selector or belongs to your intended container.
First make the operation correct. Then consider how much work it performs. The instructor's original summary is below, followed by the performance explanation.
Read more: course details, questions, and technical corrections
Source timestamps: 14:45:25–14:47:04
The instructor's first summary makes four points:
- ID lookup is fast, but avoid overusing IDs. Components share the document's ID namespace, so collisions must be avoided. Speed alone is not a reason to make every component depend on global IDs.
- Class and tag queries return live collections. He emphasizes their memory advantage but warns that repeatedly reading a large live collection could accumulate O(n²) work under his earlier cost model. This is his risk model, not the inevitable complexity of every loop.
- querySelector is usually fast enough. He says it may be slightly slower than ID lookup, but browsers heavily optimize CSS selectors, so average performance may be close. Results can differ between browsers.
- Static results can retain references to removed elements. In particular, a
querySelectorAlllist does not automatically drop an element removed from the DOM. An old list is not a live view of the current page. He again cautions against querying too many elements at once.
Editorial clarification: querySelector returns an element or null, not a collection. The live/static distinction primarily applies to collection APIs. Holding an element reference does not mean the element remains connected to the document.
Complete original English transcript (14:45:25–14:47:04)
[14:45:25] [EN] Quick summary. So the getElementsById
[14:45:28] [EN] provide the best performance but
[14:45:31] [EN] but because the browser builds the cache but you need
[14:45:34] [EN] you need to make sure that you utilize the ID space correctly.
[14:45:37] [EN] So it's basically anti-partner to you to rely on ideas
[14:45:40] [EN] IDs too much because the ID namespaces
[14:45:43] [EN] is shared across your multiple components and
[14:45:46] [EN] Just make sure that you don't overuse IDs
[14:45:49] [EN] in your app. The get elements
[14:45:52] [EN] GetElementsByClassName or GetElementsByTickName
[14:45:55] [EN] a low memory overhead because it returns a live collection
[14:45:58] [EN] collection with just a reference
[14:46:01] [EN] references to existing objects, but because it
[14:46:04] [EN] the read access is high, so
[14:46:07] [EN] You need to make sure that you utilize
[14:46:10] [EN] you utilize this collection right, because if you're going to
[14:46:13] [EN] to loop for this collection, guess it will convert to
[14:46:16] [EN] to quadratic time. And we don't want...
[14:46:19] [EN] And we don't want to do that in our app on the large set of elements.
[14:46:22] [EN] elements. The query selected
[14:46:25] [EN] selector have slightly worse performance than
[14:46:28] [EN] at getElementById, but the browser
[14:46:31] [EN] The browser itself heavily optimizes any CSS
[14:46:34] [EN] selectors. So in different browsers you may
[14:46:37] [EN] you may get different results, but on average it's very close actually.
[14:46:40] [EN] actually to get element by id but
[14:46:43] [EN] the elements that are returned from this
[14:46:46] [EN] this query to not represent the live collection so
[14:46:50] [EN] so if you remove the element from the DOM tree, you may
[14:46:52] [EN] You may end up having the stale element in your color.
[14:46:55] [EN] collection. Yeah, the sample is to the
[14:46:58] [EN] to the querySelectureAll, but
[14:47:01] [EN] because it copies all the elements you don't want
[14:47:04] [EN] wants to query too many elements with this method.
Performance: query cost, read cost, and live updates
Query cost is the work the browser does to find the elements you asked for. Before it can return a result, it needs to understand your selector and find the matches.
Think of looking for books in a library. Finding all the books about JavaScript is the query. Picking the first book from the results is a read.
const cards = document.querySelectorAll('.card'); // Query: find matching elements
const firstCard = cards[0]; // Read: take the first element from the results
| Cost | Simple meaning | In this example |
|---|---|---|
| Query cost | The work of finding the results | Find elements with the class card and return a list |
| Read cost | The work of accessing a result you already have | Get the first element from that list |
Why does a live collection bring up read cost?
Suppose you obtain a live list, change the DOM, and then read cards.length. The browser must give you the current answer. Depending on its implementation, that can involve updating or rechecking cached information before returning it.
const cards = container.getElementsByClassName('card'); // Obtain a live collection
container.appendChild(newCard); // The results may need updating
console.log(cards.length); // Must reflect the current matching elements
A static list does not need to refresh its membership after the DOM change. But if you need fresh matches, you pay for another query yourself.
Automatic updates are useful behavior, not a promise of free work. Static results are stable, not a promise that they are always faster. A live collection does not necessarily scan the whole tree on every read or run a continuous background scan. The browser controls how it keeps results current.
What can make a query do more work?
A larger search area, a more complex selector, or repeating the same query unnecessarily can add work. For example, if you only need cards inside one section, start from that section:
const section = document.getElementById('cards');
const cards = section.querySelectorAll('.card'); // Look inside this section
const firstCard = cards[0];
const secondCard = cards[1]; // Read the same results; no new querySelectorAll call
Finding the section is also a query. Keeping its reference lets you reuse it instead of finding it again. Likewise, reading cards[0] and cards[1] reuses the result list. Remember that this static list will not automatically include cards added later.
What do O(1) and O(n) mean in the lecture?
These describe how work grows as the number of elements grows, not a fixed number of milliseconds.
- O(1): roughly the same amount of work even with more elements. The instructor uses ID lookup as his example, like finding a book using an index.
- O(n): work can grow with the number of elements. Checking each of 1,000 elements can take more work than checking each of 10.
Why does the instructor mention O(n²)?
His warning combines two assumptions: you read about n elements, and each read could require about n checks to bring a live collection up to date. Under that model, the work can grow like n × n, or O(n²).
This is a way to explain potentially repeated work, not a rule that every loop over a live collection is quadratic. Browsers can cache results and update them differently. A loop that repeatedly changes matching elements may behave differently from one that only reads an unchanged collection.
What about memory?
A static result list or an array snapshot needs space to store its members' references. Both live and static collections refer to existing nodes; querySelectorAll() does not clone the nodes or connect copies through proxies.
The instructor presents live collections as having lower memory overhead. Treat that as part of his simplified comparison, not a universal ranking: actual memory use depends on the implementation and workload. Keeping an old static list can also keep removed nodes reachable while you still hold that list. Avoid retaining large results after you no longer need them.
Collection updates and reflow are different costs
Keeping a list of matching elements current is collection work. Reflow is the browser recalculating page layout. Reading a collection's length is not the same operation as reading an element's size, and an updated collection does not by itself prove that layout ran.
A DOM change can lead to both kinds of work, but do not use “live collection maintenance” and “reflow” as interchangeable terms. The insertion and template sections later explain layout separately.
What should you do in application code?
- Choose the right membership behavior. Decide whether you need the original matches or the current matches.
- Keep queries relevant. Start from your component's container and use a clear selector. Avoid overusing document-wide IDs just for speed; IDs must stay unique.
- Reuse results when they are still suitable. Do not repeatedly query an unchanged set inside a loop. Refresh static results when your task needs new matches.
- Be careful when mixing mutations and live reads. Repeated changes can shift indexes and may add maintenance work. Use a snapshot when processing a fixed batch.
- Measure when performance matters. Use browser performance tools on the real interaction, with realistic element counts and DOM changes. Compare the complete operation, including snapshot creation or repeated queries, rather than assuming one API always wins.
The instructor's main point is to consider the whole operation: finding results, reading them, keeping them current, and storing them. For typical application work, he considers selector APIs sufficient; when live behavior is useful, use it deliberately and keep the working set manageable.
There are three more questions in this part of the class:
| Student's question | What should you remember first? |
|---|---|
| Why offer a list that does not update automatically? | Not every program needs changing results. The instructor considers selector APIs sufficient for most applications. |
| Why does a list of existing elements need memory? | Recording which elements matched takes space, but it does not mean copying every element. |
| Is the same query always faster the second time? | No. The browser may reuse earlier work, but that is not guaranteed. |
The instructor's answer about “element copies” needs a correction. His full explanation, the correction, and a verification example are preserved below for comparison with the course.
Read more: course details, questions, and technical corrections
Source timestamps: 14:47:07–14:50:38
Question: How does querying complexity differ from read cost?
Using getElementsByTagName, the instructor explains query cost as the work of finding matching elements, potentially visiting every element. Read cost is the work of accessing a member after obtaining the collection. He argues that a live collection must verify that elements remain in the DOM, potentially requiring another traversal. This continues his classroom model; liveness alone does not establish whether or when an actual engine repeats a query.
Question: Why does querySelectorAll return a non-live list?
He calls live collections a legacy design that is generally less advisable because careless use can cause problems. When explaining why a static alternative exists, he explicitly says “I guess”: he suggests that developers needed an alternative. He believes most application developers do not need to manage live collections and are better served by selector APIs. His uncertainty is preserved here rather than presented as established API design history.
Question: If these are references to existing nodes, why does memory usage increase?
The instructor answers “Not exactly.” He claims the browser creates new objects with the same values and connects them to the originals through a proxy mechanism, so changing the copies affects the originals. He contrasts this with a live collection reusing the actual references.
Technical correction: that explanation is incorrect. A static querySelectorAll list contains the original nodes, not node copies synchronized through proxies. The additional storage is for the result list and its references. See the WHATWG querySelectorAll definition. This verification example is an editorial addition, not the instructor's original code:
const host = document.createElement('div');
host.innerHTML = '<p class="item">Before</p>';
const items = host.querySelectorAll('.item');
console.log(items[0] === host.firstElementChild); // true: the same node
items[0].textContent = 'After';
console.log(host.firstElementChild.textContent); // After
items[0].remove();
console.log(items.length); // 1: membership has not updated
console.log(host.querySelectorAll('.item').length); // 0: a fresh query
Question: Class names can change. Is a later query guaranteed to be faster because of caching?
He says no: application developers do not control engine optimizations, and the same method can behave differently in Safari and Chrome. He also gives an example claiming that Safari compiles native selectors using WebAssembly while Chrome does not. He then emphasizes that caching logic is complex and results depend on the engine.
Unverified instructor claim: The transcript supplies no evidence for the Safari/WebAssembly implementation detail, so it is not treated as a technical conclusion. The useful takeaway is to avoid assuming a fixed performance ranking across browsers or guaranteeing that repeated queries hit a cache.
Complete original English transcript (14:47:07–14:50:38)
[14:47:07] [EN] Could you clarify the difference between the time
[14:47:10] [EN] complexity of querying versus the read cost.
[14:47:13] [EN] Yeah, okay. So if we'll go back
[14:47:16] [EN] back to get elements by Tegna
[14:47:19] [EN] for instance. So the time comes
[14:47:22] [EN] complexity it takes to go through the elements
[14:47:25] [EN] to find all the elements that we are looking for,
[14:47:28] [EN] is big of an because we need to query
[14:47:31] [EN] we need to go through all elements in the worst case.
[14:47:34] [EN] but the read cost is also
[14:47:37] [EN] also bigger of n because
[14:47:40] [EN] we maintain the life collection and when we maintain
[14:47:44] [EN] maintain a live collection, this means the browser needs
[14:47:46] [EN] needs to verify that the element exists
[14:47:50] [EN] in the DOM tree. So every time you're trying to
[14:47:53] [EN] access this life collection, the browser queries the element
[14:47:56] [EN] again to verify that the elements still
[14:47:58] [EN] still present. That's why it can result
[14:48:02] [EN] result to the time complexity being, in the worst case, we'll need
[14:48:05] [EN] we'll need to parse all elements again. Is there a reason
[14:48:07] [EN] the query selector all returns the
[14:48:10] [EN] non-live element list compared
[14:48:13] [EN] compared to this one, for example, comparing or
[14:48:17] [EN] returning the live? - So the
[14:48:19] [EN] The Life Collection is actually a legacy one.
[14:48:22] [EN] It's not recommended to use for
[14:48:25] [EN] normal cases. And I guess
[14:48:29] [EN] Yes, the query selector returns non-live collection,
[14:48:31] [EN] because we needed to have some alternative.
[14:48:34] [EN] The live collection has more drawbacks and if
[14:48:37] [EN] if not used wisely, can lead to more issues.
[14:48:41] [EN] issues. But in our application development, we
[14:48:44] [EN] we don't really need to manage the live collections and for most
[14:48:47] [EN] most of the developers, the queries lecture is
[14:48:49] [EN] the better I go because it doesn't have
[14:48:53] [EN] the expensive read access.
[14:48:56] [EN] Why would query selector all
[14:48:58] [EN] impact memory, we're returning references to
[14:49:01] [EN] notes which already exist in the DOM.
[14:49:04] [EN] Not exactly. We actually
[14:49:07] [EN] return the copies of the
[14:49:11] [EN] basically the browser creates a new object with a
[14:49:13] [EN] the same values and the copies and when you
[14:49:16] [EN] change the properties of this object
[14:49:20] [EN] objects it will impact the region
[14:49:22] [EN] regional object. But, uh...
[14:49:26] [EN] it's still we have two separate objects basically with a
[14:49:29] [EN] with the same properties and they are connected with each other for the proxy
[14:49:32] [EN] proxy mechanism. So it's a slightly different way.
[14:49:35] [EN] When we use the live collection, we actually guarantee
[14:49:38] [EN] we can actually reuse the real reference to the object.
[14:49:40] [EN] object instead.
[14:49:44] [EN] When you're saying that the cash would
[14:49:47] [EN] would make things faster next time. Are you...
[14:49:49] [EN] always sure about that because
[14:49:53] [EN] because class names can be dynamic.
[14:49:55] [EN] So you can't control that
[14:49:59] [EN] that because it's up to the browser engine.
[14:50:02] [EN] it's up to develop it the same query methods can
[14:50:05] [EN] can behave differently in Safari or Chrome.
[14:50:08] [EN] depending on how developers optimize that. For instance,
[14:50:11] [EN] For instance, Safari browser uses the
[14:50:14] [EN] WebAssembly compilation for the native selectors while the
[14:50:17] [EN] the Chrome doesn't use that because they perform
[14:50:20] [EN] they find that the performance of existing queries,
[14:50:23] [EN] electricity is fine on average.
[14:50:26] [EN] can't guarantee the same results and it's up to the
[14:50:29] [EN] to the engine. So the logic of caching the
[14:50:32] [EN] the element, the class, the query selectors
[14:50:35] [EN] is quite complex and yeah, no guarantee.
[14:50:38] [EN] tea.
Reduce query work by starting inside your container
Suppose the page has cards, navigation, a sidebar, and a footer. If you only need card titles, find the card container first and then search inside it:
const container = document.getElementById('cards');
const titles = container.querySelectorAll('.card-title');
The first line gets the container. The second finds matching elements within it. If you later need card bodies, you can keep using the same container.
The instructor uses a page with 10,000 elements to illustrate this: when you know which area contains your targets, start there. He also suggests keeping selectors simple so the matching conditions stay clear and unnecessary query work is avoided.
Focus on the approach rather than memorizing a number of milliseconds per query. His numerical examples and their limitations are below.
The original transcript has a gap between 14:52:17 and 14:55:38; no content is supplied for that interval.
Read more: course details, questions, and technical corrections
Source timestamps: 14:50:40–14:52:17
The instructor says the most obvious and often overlooked optimization is simplifying selectors. Complex selectors require more processing. Even small costs can add up across many queries.
He gives spoken examples of “5 ms” and “hundreds of queries” accumulating to roughly “50 ms.” These are classroom illustrations without measurement conditions, and they do not form a consistent direct multiplication. His point is that small costs and optimizations can accumulate into noticeable differences.
His second suggestion is to query from a smaller container. Although he discourages overusing IDs, a main container can be a reasonable exception. Suppose the full DOM has 10,000 elements: find a section by ID, retain its reference, and run repeated queries within that section.
// Example added to illustrate the course concept
const section = document.getElementById('cards');
const titles = section.querySelectorAll('.card-title');
He describes this as locating the section first and reducing later work. His closing phrase “just one operation” should not be interpreted as making every complex query within a container O(1).
Source gap: The original file jumps from 14:52:17 to 14:55:38. No missing teaching content has been invented to fill this interval.
Complete original English transcript (14:50:40–14:52:17)
[14:50:40] [EN] Let's move on to
[14:50:44] [EN] to performance best practices.
[14:50:47] [EN] The first and the most obvious one and
[14:50:50] [EN] like the one overlooked uh
[14:50:53] [EN] the most is that we can actually improve the performance
[14:50:56] [EN] of querying by simplifying this lecture.
[14:50:59] [EN] So each time you use the complex selector,
[14:51:02] [EN] the CSS compiler needs to translate
[14:51:05] [EN] transpile this and it takes more time so you pay
[14:51:08] [EN] pay extra tax on using the complex selectors.
[14:51:11] [EN] So you may notice that it's
[14:51:14] [EN] it's not significant so you may pay like five milliseconds
[14:51:17] [EN] on the query to compile the selection
[14:51:20] [EN] lecture but all these things are
[14:51:23] [EN] accumulating. So if you do hundreds of queries,
[14:51:26] [EN] So it will result maybe to 50 milliseconds
[14:51:29] [EN] milliseconds on average in the delay.
[14:51:32] [EN] If we combine all possible optimizations,
[14:51:35] [EN] with this one, the results can be quite significant.
[14:51:38] [EN] Although the ID is not the best practice, you still can
[14:51:41] [EN] can utilize IDs for the core container when
[14:51:44] [EN] For instance, you need to query the
[14:51:47] [EN] elements from the very large tree.
[14:51:50] [EN] if we have some DOM tree that has
[14:51:54] [EN] as 10,000 elements, if you query from HTML,
[14:51:56] [EN] then you need to go through 10,000 L
[14:51:59] [EN] elements on the first query but if you're from
[14:52:02] [EN] for instance, could select this section with an ID
[14:52:05] [EN] and then you know that this operation should be repeated
[14:52:08] [EN] multiple times, then you can just provide an end
[14:52:11] [EN] and query from this section, and it will
[14:52:14] [EN] just one operation. So next section is adding
[14:52:17] [EN] removing
Where does this knowledge fit if you already use React?
A student asks the same question: if React or Angular already manages the DOM, what should an application developer do?
The instructor's answer is: usually let the framework handle its part; if you write DOM queries yourself, start with your own code. For example, code that directly queries elements inside a React effect may benefit from the ideas above.
Learning the DOM API does not mean you need to rewrite React's internals. The instructor points out that framework internals are complex.
Another student asks whether selectors are only processed when CSS loads. The instructor explains that when JavaScript reaches querySelector(...), the browser also needs to interpret the matching condition you pass in. That work happens while the program runs, not just when page styles first load.
Read more: course details, questions, and technical corrections
Source timestamps: 14:55:38–14:57:03
Question: When using React or Angular, should you investigate or optimize their internal DOM methods?
The student asks whether the earlier low-level discussion leads to recommendations for framework users, or whether they should trust that library authors have spent time on it.
The instructor says that when a library manages the DOM, you generally rely on it. A possible area for improvement is a DOM query you write inside a React effect. Trying to optimize React's internals is not a good starting point because they are complex. The transcript's “SUMIF effects” is interpreted as React effects from context; the precise spoken wording cannot be confirmed.
Question: Is CSS cost only paid at compile time? Do complex selectors also cost time at runtime?
He distinguishes processing CSS during page initialization from passing a dynamic selector to querySelector in JavaScript. The latter appears at runtime, so querying also involves selector processing then. He describes paying that cost each time the query runs.
Editorial note: This distinguishes stylesheet processing from dynamic queries. It should not be expanded into a guarantee that every engine fully recompiles every selector on every call without caching. Earlier, the instructor also acknowledges that optimizations vary by browser.
Complete original English transcript (14:55:38–14:57:03)
[14:55:38] [EN] moving elements.
[14:55:42] [EN] And before we jump to that, any questions so far?
[14:55:44] [EN] So you talked about these,
[14:55:47] [EN] understanding the differences between these
[14:55:51] [EN] these methods being useful if you were going to build something more low level
[14:55:53] [EN] if you are working on something that is used
[14:55:57] [EN] using a framework like React or Angular.
[14:56:00] [EN] are there recommendations you would make for
[14:56:03] [EN] improving performance or like knowing what their
[14:56:06] [EN] their methods are using under the hood? Or do you just
[14:56:09] [EN] you just kind of trust that the library authors have spent a lot of time
[14:56:12] [EN] which I'm thinking about. - Yeah, when you utilize
[14:56:15] [EN] any library that manages the DOM for you
[14:56:18] [EN] you just rely on this library. The only way
[14:56:21] [EN] way you can possibly think you may use
[14:56:24] [EN] use the DOM API within the SUMIF
[14:56:27] [EN] effects in the React when you need to query things. This is the
[14:56:30] [EN] This is the place where you could optimize potentially.
[14:56:33] [EN] trying to optimize a React is not a good
[14:56:36] [EN] good idea because it's quite complex inside. Is the cost
[14:56:39] [EN] of CSS only during compile.
[14:56:42] [EN] compile time when the runtime of complex select
[14:56:45] [EN] selectors also be bad on performance?
[14:56:48] [EN] performance? No, actually, you compile this...
[14:56:51] [EN] First you compile your styles in CSS.
[14:56:54] [EN] when you initiate the page. But when you execute the job,
[14:56:57] [EN] JavaScript with some query selector. This is dynamic selector.
[14:57:00] [EN] This means it should be compiled in runtime.
[14:57:03] [EN] So you pay the cost every time you execute that.
Once you have a container, how do you add content?
Now that you can find elements, the next step is to put new content on the page. Compare these two approaches:
const container = document.getElementById('cards');
// Approach 1: give the browser HTML text to replace the container's contents
container.innerHTML = '<p>First card</p>';
const container = document.getElementById('cards');
// Approach 2: create an element, fill in its text, and insert it
const card = document.createElement('p');
card.textContent = 'Another card';
container.appendChild(card);
The first approach supplies an HTML string, which the browser must parse into elements. The second creates a node directly, then uses appendChild to put it at the end of the container.
The instructor suggests reusing a template when you repeatedly create the same structure, instead of assembling HTML strings each time. That is what the upcoming card exercise does.
He also mentions reflow: recalculating page layout. Adding a card may push later content down, giving the browser more work. But each change does not necessarily recalculate the entire page immediately; browsers may process several changes together.
Read more: course details, questions, and technical corrections
Source timestamps: 14:57:06–14:58:42
The instructor uses “every method is bad” to emphasize that modifying the DOM has a cost and that unnecessary reflows should be minimized. He compares methods that parse HTML strings with methods that insert existing node objects.
| Method | Course explanation |
|---|---|
element.innerHTML = html | Sets the element's HTML content; the browser parses the string, processes the HTML, and updates the DOM |
element.insertAdjacentHTML(position, html) | Also takes raw HTML text, so parsing is still needed |
element.insertAdjacentElement(position, node) | Takes an existing Element, avoiding reparsing that content from an HTML string |
parent.appendChild(node) | Inserts an existing node into a parent; recommended in the course for dynamic insertion |
He considers a one-time innerHTML assignment during initialization an option, but suggests working directly with nodes for repeated dynamic insertion. His phrase “compiled HTML object” is rendered here as an already-created DOM object.
Technical correction: A DOM mutation does not mean an immediate full reflow on every call. Browsers schedule and combine rendering work. Whether layout is needed depends on the change, and some geometry reads can force immediate layout. The practical direction is to reduce unnecessary DOM and layout work, not to assign a fixed number of reflows to each API. See web.dev: avoiding layout thrashing.
Complete original English transcript (14:57:06–14:58:42)
[14:57:06] [EN] So let's now understand how we can add or remove
[14:57:09] [EN] elements. And when we
[14:57:12] [EN] when it comes to aiding or removing every method
[14:57:15] [EN] method is bad. So basically, we don't have a good
[14:57:18] [EN] good. So the performance in
[14:57:21] [EN] impact is always significant. So there is no
[14:57:24] [EN] of good methods to insert data because
[14:57:27] [EN] every time we modify the DOM it triggers the
[14:57:29] [EN] the reflow.
[14:57:33] [EN] in our application we want to minimize the refresh
[14:57:36] [EN] reflow operation. And so, but
[14:57:39] [EN] But there are a few methods that are particularly
[14:57:42] [EN] impact the performance. The first one is inner HTML.
[14:57:45] [EN] So when you use the inner HTML, you basically
[14:57:48] [EN] you basically set the HTML of the element
[14:57:51] [EN] the browser needs to involve the HTML
[14:57:54] [EN] HTML parser to compile your HTML again
[14:57:57] [EN] to do the full reflow, validate your
[14:58:00] [EN] your HTML and so on. So you pay the cost of the
[14:58:03] [EN] the parsing. Same applies to insert adjacent
[14:58:06] [EN] in HTML because we use the raw
[14:58:09] [EN] raw HTML, the browser still needs to parse that.
[14:58:12] [EN] step. But if you need
[14:58:15] [EN] need to modify the DOM make sure that you don't
[14:58:18] [EN] you don't utilize linear HTML too often.
[14:58:21] [EN] to use this once per perinusus
[14:58:24] [EN] stylization but if you
[14:58:27] [EN] you expected to insert elements dynamically
[14:58:30] [EN] then it's better to utilize the insert_adjson_l
[14:58:33] [EN] element or append child because it takes
[14:58:36] [EN] It takes the compiled HTML object
[14:58:39] [EN] already and just renders this on a DOM
[14:58:42] [EN] DOM, and we can see how it works.
Where should the element go, and how do you remove it?
appendChild puts a node at the end of a container. To insert an element before, after, or inside a specific target, you can use insertAdjacentElement.
For a target card, the four positions look like this:
beforebegin → outside the card, before it
┌─ Card ─────────────────────────┐
│ afterbegin → inside, at start │
│ Existing content │
│ beforeend → inside, at end │
└───────────────────────────────┘
afterend → outside the card, after it
const card = document.createElement('article');
card.textContent = 'This is a card';
const note = document.createElement('p');
note.textContent = 'An additional note';
card.insertAdjacentElement('beforeend', note); // Put the note last inside the card
The instructor also covers removal. These two actions have different scopes:
card.remove(); // Remove the whole card
container.innerHTML = ''; // Clear its contents, keeping the container itself
When reading an API name, ask whether it acts on the element itself or on the content inside it. That helps keep the operations distinct.
Read more: course details, questions, and technical corrections
Source timestamps: 14:58:45–14:59:39
The instructor demonstrates the insertion positions and emphasizes the flexibility of insertAdjacentElement:
| Position | Where the element is inserted |
|---|---|
beforebegin | Before the target, as its preceding sibling |
afterbegin | Inside the target, as its first child |
beforeend | Inside the target, as its last child |
afterend | After the target, as its following sibling |
beforebegin
<target>
afterbegin
…existing content…
beforeend
</target>
afterend
To remove an element, obtain a reference to it and call remove(). To clear a container's contents, assign an empty string to innerHTML.
// Examples reconstructed from the lesson: remove an element versus clear contents
card.remove();
container.innerHTML = '';
The second operation keeps the container itself and removes what is inside.
Complete original English transcript (14:58:45–14:59:39)
[14:58:45] [EN] So the method is pretty flexible because
[14:58:48] [EN] because it accepts the position where we can insert the element
[14:58:51] [EN] The first one is Before Begin, which basically
[14:58:54] [EN] basically insert the element before the
[14:58:57] [EN] the target element. So we can try to insert that and
[14:59:00] [EN] and the element is distorted. So the afterbegin,
[14:59:03] [EN] will give you the first channel position.
[14:59:06] [EN] Why?
[14:59:09] [EN] While the before-and, what
[14:59:12] [EN] or will render element as a lust child.
[14:59:15] [EN] And after "End" will give you the position right
[14:59:18] [EN] right after the target element. So this method is for
[14:59:21] [EN] that's pretty flexible and can be used
[14:59:24] [EN] almost anywhere.
[14:59:27] [EN] Then how do we remove elements?
[14:59:30] [EN] removing elements can be done through the remove
[14:59:33] [EN] basically we need to have a reference to the
[14:59:36] [EN] to the object, and we can also reset the whole HTML
[14:59:39] [EN] HTML by setting the empty string.
Build a card: prepare a template, then fill in the data
The exercise has a simple goal: give a function a title and body text, and let it create a card.
createCardComponent('My project', 'This is the website I recently built.');
Each card has the same structure, with different text. The instructor stores that shared structure in a template:
<template id="card-template">
<article class="card">
<h3 class="card-title"></h3>
<div class="card-body-content"></div>
</article>
</template>
The card inside the template does not appear directly on the page. When you need one, copy it, fill in its data, and then insert it.
Step 1: find the template and copy its contents
const template = document.getElementById('card-template');
const copy = template.content.cloneNode(true);
const card = copy.firstElementChild;
Read it one line at a time: template.content gives you the template contents; cloneNode(true) copies them along with their child nodes; firstElementChild gets the first element in that copy, the article.
The contents are held in a DocumentFragment. For now, think of it as a container holding nodes that have not been inserted into the current page.
Step 2: find the title and body inside the new card
These queries are written separately for readability. The instructor uses one querySelectorAll call to get both elements; that version is preserved in the next section.
const cardTitle = card.querySelector('.card-title');
const cardBody = card.querySelector('.card-body-content');
cardTitle.textContent = 'My project';
cardBody.textContent = 'This is the website I recently built.';
Step 3: put the card on the page
Assuming the page already contains <main id="cards"></main>:
const container = document.getElementById('cards');
container.appendChild(card);
The sequence is: copy the template → fill in the data → insert into the page. The original template remains available for the next card.
The error the instructor encounters live
He initially searches from document for the new card's title. The query fails, and assigning the text produces an error. The card has not been inserted into the page yet!
Searching from card fixes it. This is a useful lesson from the exercise: when working on an element that has not been added to the page, search from the element you already have.
Read more: course details, questions, and technical corrections
Source timestamps: 14:59:46–15:05:49
The assignment, setup, and initial idea
The live-coding task is to implement createCardComponent(title, body). Both arguments are strings. Fill the card's h3 and body content, return an HTML element, and let the caller append it to a container.
The transcript alternates between createCartComponent, car, and cart. These notes normalize the name to card based on context. The original English wording remains in the transcript below.
Students are told to get the repository from the chat or slides, open begin/index.html, and use their preferred editor. No compiler or complex setup is needed. The attachment contains neither the actual repository link nor the complete code shown on screen.
On opening the file, the instructor points out the container, the HTML stored as a string, and the empty card function. He considers setting container.innerHTML directly but prefers a reusable representation over string manipulation, so he switches to HTML's native template element.
Moving markup into a template
He removes the HTML string and places a template with an ID in the head. He describes its contents as a lightweight HTML representation held in memory rather than rendered immediately. The template element itself can be found, while ordinary document queries do not enter its content tree.
Technical correction: His claim that no DOM API or external script can access template contents is too broad. template.content exposes a DocumentFragment that can be queried directly. Templates are not a security boundary; their contents simply do not directly become part of the rendered document subtree. See the HTML Standard: template.
Copying, querying, filling, and returning
- Find the card template with
document.getElementById. - Read
template.contentto obtain its DocumentFragment. - Call
cloneNode(true). Thetruerequests a deep copy including descendants. - The copy is a fragment, so use
firstElementChildto obtain thearticlecard. - Find the card title and body content. The instructor supplies two selectors in one query and takes the two results.
- Assign
titleandbodyto the elements' respectivetextContentproperties. - Return the card and let the caller append it to the container.
The live error and fix
The instructor first uses document.querySelectorAll(...) to find content in the copied card. Running the example fails with an undefined value when setting textContent.
He spots the incorrect query scope: the new card is not connected to the document yet. Changing the query to card.querySelectorAll(...) makes the card render with its title and body filled in.
Having a reference to a detached node does not mean a query from the main document can find it.
Complete original English transcript (14:59:46–15:05:49)
[14:59:46] [EN] We're going to
[14:59:48] [EN] do some live coding, very simple
[14:59:51] [EN] exercise. So we have some HTML
[14:59:54] [EN] HTML template that we need to utilize.
[14:59:57] [EN] and we have our createCartComponent function.
[15:00:00] [EN] So we need to implement the function
[15:00:03] [EN] It takes the title and the body, this is the string variables,
[15:00:06] [EN] and sets it to the
[15:00:09] [EN] the age-free tech and card body content.
[15:00:12] [EN] There are multiple ways how can you
[15:00:15] [EN] implement this but let's see what we
[15:00:18] [EN] we can do here. And here we call this function
[15:00:21] [EN] function, so this function should determine HTML elements
[15:00:24] [EN] and then we append this element to the container
[15:00:27] [EN] So to start with exercise, check out
[15:00:30] [EN] repository. I hope you will have the
[15:00:33] [EN] the links to your repository in the chat or
[15:00:36] [EN] You can also click on the slides.
[15:00:39] [EN] and open the folder begin and open
[15:00:42] [EN] open the file index.html. We don't need any--
[15:00:46] [EN] compiler or complex setup, it will be enough
[15:00:49] [EN] enough just to work with the HTML, use your favorite
[15:00:51] [EN] favorite editor. And now I'm going to
[15:00:54] [EN] switch to the code. So I opened
[15:00:57] [EN] the index.html. So here we have
[15:01:01] [EN] We have our container, then we have our
[15:01:04] [EN] HTML, which is right now stored as a string.
[15:01:06] [EN] string. And also we have our
[15:01:09] [EN] or HTML element.
[15:01:12] [EN] the function that creates our cart element.
[15:01:16] [EN] Right now it's empty, so our goal will be to implement this.
[15:01:18] [EN] So what can we do here?
[15:01:21] [EN] We could potentially just say
[15:01:24] [EN] for instance, container, and then
[15:01:27] [EN] and then set the inner HTML.
[15:01:30] [EN] We would have to work with the HTML director, with the
[15:01:33] [EN] string directly and representing the HTML
[15:01:36] [EN] as a string is not the best way to do things.
[15:01:39] [EN] So we want to have some kind of reusable
[15:01:42] [EN] way, how can we represent the templates? So,
[15:01:46] [EN] So, and apparently in HTML, we have the special
[15:01:49] [EN] a special tag called template.
[15:01:52] [EN] So we can remove this line.
[15:01:55] [EN] And in the head, I'm going to use the template tag
[15:01:57] [EN] so what this template does
[15:02:00] [EN] it basically stores your edge
[15:02:03] [EN] HTML in the lightweight uh
[15:02:06] [EN] object in memory.
[15:02:09] [EN] So this content
[15:02:12] [EN] is not querible by any DOM API.
[15:02:15] [EN] so any external scripts will not be able to
[15:02:18] [EN] to access this content.
[15:02:22] [EN] So it's also not rendered on the final render
[15:02:25] [EN] So if a user tries to traverse the DOM
[15:02:28] [EN] the Dome Tree. They will not be able to
[15:02:31] [EN] be able to see that this template is utilized.
[15:02:34] [EN] So it's kind of a lightweight representation of our
[15:02:37] [EN] cart. So we edit the cart template into the
[15:02:40] [EN] template tag and we're also assigned the ID
[15:02:43] [EN] ID. So the content of the template is not querible, but
[15:02:46] [EN] but the template is, so we need to clone the con
[15:02:49] [EN] content of the template. So let's update
[15:02:52] [EN] Let's first query the template.
[15:02:55] [EN] So we can do document, get
[15:02:58] [EN] GetElementById.
[15:03:01] [EN] And then we use our car.
[15:03:04] [EN] CarTemplate. So now we
[15:03:07] [EN] Now we have the reference to the current template.
[15:03:10] [EN] Let's clone the content.
[15:03:13] [EN] content as we know the templates use a fragment
[15:03:16] [EN] So we need to clone the content of the fragment.
[15:03:18] [EN] for the special method that that
[15:03:22] [EN] that I'm going to show now. So we're going to access it
[15:03:25] [EN] the template and the template has the property called
[15:03:28] [EN] content so the content gives us the
[15:03:31] [EN] the fragment that we need to work
[15:03:34] [EN] work with. So then I'm going to use the clone
[15:03:37] [EN] node and I'm providing the
[15:03:40] [EN] the true flag to do the deep clone.
[15:03:43] [EN] and then I used
[15:03:46] [EN] the first element channel
[15:03:49] [EN] child that will return me the article. So by default
[15:03:52] [EN] First, it will return the fragment, but we need to
[15:03:55] [EN] to access this element. And this element is the
[15:03:58] [EN] the element. And that's why we use the first
[15:04:01] [EN] element child. So now we have our article
[15:04:04] [EN] we want to update the article content.
[15:04:07] [EN] So we need to set the car title
[15:04:10] [EN] and the card body content. So let's query that.
[15:04:13] [EN] that. So let's call it
[15:04:16] [EN] card title and card body
[15:04:19] [EN] body and then we use the document query
[15:04:22] [EN] query select your all.
[15:04:25] [EN] And then we're providing to
[15:04:28] [EN] selectors. The first one is the cart title
[15:04:31] [EN] Then we need to post a
[15:04:33] [EN] car
[15:04:37] [EN] start body content class.
[15:04:40] [EN] So now we're selected to
[15:04:43] [EN] two elements, card title and the card body, and now we want
[15:04:46] [EN] we want to update the text content of this.
[15:04:49] [EN] elements. So we use the text content property and then
[15:04:52] [EN] assign title. Then we need to use
[15:04:55] [EN] card body and then assign
[15:04:58] [EN] assigned text content to the
[15:05:01] [EN] body variable. Okay.
[15:05:04] [EN] OK, now we return the cart.
[15:05:07] [EN] And if we run this example,
[15:05:10] [EN] So now we're creating a new cart element.
[15:05:13] [EN] and then we're returning this card element
[15:05:16] [EN] from the Create Card Component function
[15:05:19] [EN] assign some random text and if you run the browser
[15:05:22] [EN] we see that
[15:05:25] [EN] that this doesn't work, and let's see why.
[15:05:28] [EN] And apparently, where it's
[15:05:31] [EN] We're trying to set the text content
[15:05:34] [EN] So the property undefined. So probably we have a mistake.
[15:05:37] [EN] mistake and how we query things. Yeah, so we need
[15:05:40] [EN] So we don't need to query the document. We need to query cart.
[15:05:43] [EN] So if we fix that.
[15:05:46] [EN] Yeah, now we have our cart rendered
[15:05:49] [EN] and all elements set. And this is it for the
Wrap the steps in a function to create more cards
The previous section created one card. Put the copying, text assignments, and return statement in a function, and you can supply different text each time.
// Reuse template and container from the previous section
function createCardComponent(title, body) {
const copy = template.content.cloneNode(true);
const card = copy.firstElementChild;
card.querySelector('.card-title').textContent = title;
card.querySelector('.card-body-content').textContent = body;
return card;
}
const firstCard = createCardComponent('Project one', 'My personal website');
const secondCard = createCardComponent('Project two', 'My to-do list');
container.appendChild(firstCard);
container.appendChild(secondCard);
return card hands the element back to the caller. appendChild inserts it into the page. The function prepares a card, and its caller decides where to put it.
The instructor compares this approach with putting placeholders in an HTML string and replacing them with replace. He prefers keeping card structure in an HTML template and using JavaScript to fill in the data for reuse.
The expandable notes below contain a complete reconstructed example you can save as index.html, along with the solution discussion. It was reconstructed from the transcript, not supplied as original source code in the attachment.
Read more: course details, questions, and technical corrections
Source timestamps: 15:05:52–15:07:07
The instructor reviews two approaches. The first uses placeholders in an HTML string, substitutes values with replace, and inserts the result through innerHTML. This requires manipulating strings and parsing HTML again when the operation repeats.
The course solution uses the template's DocumentFragment: manipulate content separately from the main document, reuse the template, keep component markup in HTML where it naturally belongs, and let JavaScript fill in the data.
This complete example is reconstructed from the spoken explanation, not copied verbatim from the instructor's source. IDs, classes, and sample text were supplied during note preparation. It preserves the deep clone, firstElementChild, card-scoped query, and textContent assignments.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>DOM API — Card template</title>
<template id="card-template">
<article class="card">
<h3 class="card-title"></h3>
<div class="card-body-content"></div>
</article>
</template>
</head>
<body>
<main id="cards"></main>
<script>
const template = document.getElementById('card-template');
const container = document.getElementById('cards');
function createCardComponent(title, body) {
const card = template.content.cloneNode(true).firstElementChild;
const [cardTitle, cardBody] = card.querySelectorAll(
'.card-title, .card-body-content'
);
cardTitle.textContent = title;
cardBody.textContent = body;
return card;
}
container.appendChild(
createCardComponent('DOM API notes', 'Create the first card from a template.')
);
container.appendChild(
createCardComponent('Second card', 'Each clone creates an independent card.')
);
</script>
</body>
</html>
Editorial clarification: A comma-separated selector returns results in document order, not the order in which selectors are written. Destructuring works here because the title precedes the body in the template. If the markup order changes, adjust the assignments or query each element separately. The original template contents remain available for another clone.
Complete original English transcript (15:05:52–15:07:07)
[15:05:52] [EN] first exercise. Okay, let's have the quick
[15:05:56] [EN] quick look of how could we potentially solve that.
[15:05:58] [EN] The first solution would involve
[15:06:01] [EN] had treated the article as a
[15:06:04] [EN] string so we could create a placeholders within the
[15:06:07] [EN] within the context and just replace it with a
[15:06:11] [EN] replace function, but this means we would need to work with a string.
[15:06:13] [EN] And then we could append
[15:06:16] [EN] append the content using the inner HTML, but the
[15:06:19] [EN] This is not the best way because
[15:06:22] [EN] We will have to parse the content
[15:06:25] [EN] content here in inner HTML and if we
[15:06:28] [EN] if we want to repeat this operation, then it's not
[15:06:31] [EN] the most efficient way. So what we did is we just
[15:06:35] [EN] We just use the document fragment, which is the lightweight representation.
[15:06:37] [EN] representation of HTML element.
[15:06:41] [EN] Since it's shielded from causing the rift
[15:06:44] [EN] In Reflow, we can do whatever we want in memory.
[15:06:47] [EN] So it can also be reused many times and it says isolated
[15:06:50] [EN] from the queries. And yeah, we utilize
[15:06:52] [EN] utilize it to keep our markup component in
[15:06:55] [EN] HTML which is a very natural play
[15:06:58] [EN] place where we should keep such things.
[15:07:01] [EN] and then we just update it
[15:07:04] [EN] data level function and the solutions
[15:07:07] [EN] and here is the final solution.
Why prepare the card before inserting it?
The final student question is why editing the template copy does not cause the current page to lay out again.
The new card has not been connected to the page yet. Changing its title and body does not require the browser to move existing page content for that still-hidden card.
Copy the template
→ Fill in the title and body: the new card is not on the page yet
→ appendChild: insert it, which may now affect page layout
This is the workflow the instructor encourages: prepare the content, then insert it. Do not memorize it as “appendChild always triggers one immediate reflow”; the browser still schedules the actual layout work.
Read more: course details, questions, and technical corrections
Source timestamps: 15:07:10–15:07:59
A student asks for a brief repeat of why this approach does not trigger reflow.
The instructor explains that the fragment is not in the main DOM tree; it is held in memory. Modifying these disconnected nodes does not require recalculating the current page's layout. Updating an element already in the document can affect layout. The exercise prepares the card separately and connects it to the main document with appendChild afterward.
He says that the only operation causing reflow is appendChild. Editorial correction: In this example, the statement contrasts detached preparation with insertion. It does not mean appendChild must synchronously trigger exactly one reflow, or that other DOM APIs cannot affect layout. Browsers may delay and combine layout calculations; see the earlier rendering-cost discussion.
Complete original English transcript (15:07:10–15:07:59)
[15:07:10] [EN] Before we jump to the next section,
[15:07:13] [EN] Do we have any question?
[15:07:17] [EN] Can you just explain again briefly why doing it that way doesn't trigger
[15:07:19] [EN] trigger reflow? Yeah. So...
[15:07:23] [EN] So when you modify the
[15:07:25] [EN] fragment.
[15:07:29] [EN] It's not within the DOM tree, it's actually just in memory.
[15:07:31] [EN] So when you update any properties,
[15:07:35] [EN] so it doesn't trigger any reflow. So if you could
[15:07:38] [EN] could potentially have the element in the DOM tree
[15:07:40] [EN] and you start updating
[15:07:44] [EN] the elements of the the properties of this element then this
[15:07:46] [EN] this would cause reflow because the element already
[15:07:50] [EN] already in a DOM tree. But since it's in memory, yeah,
[15:07:53] [EN] Yeah, we can do any operations and
[15:07:56] [EN] It's disconnected from the main DOM tree.
[15:07:59] [EN] The only thing that counts the reflow is when we use a bandchild.
What can you build after this lesson?
Try making a minimal project list: prepare a card template, call the function to create two cards, change one card's title, and remove the other.
| What you want to do | Tools from this lesson |
|---|---|
| Find an element | querySelector or getElementById |
| Find several elements | querySelectorAll |
| Change an element's text | textContent |
| Reuse a structure | template, cloneNode(true) |
| Insert a node into a container | appendChild |
| Choose an insertion position | insertAdjacentElement |
| Remove an element | remove |
The instructor closes by saying selector APIs work well for most situations. In the less common cases where you need a live collection, pay attention to its size and how you use it. The full performance discussion remains in the expandable notes throughout the page.
This page covers the supplied transcript through 15:08:50.
Read more: course details, questions, and technical corrections
Source timestamps: 15:08:02–15:08:50
The instructor concludes that the DOM API is fairly simple, with a limited set of methods, and useful for low-level development. However, developers should consider its limitations and costs.
He repeats his warning against querying a large set and repeatedly reading a live HTMLCollection. Under his cost model, that may accumulate quadratic work. He considers querySelector fast enough for most cases and broadly usable. In the rare cases that need a live collection, he recommends keeping the working set small to reduce potential performance impact.
The attachment's final sentence ends at 15:08:50, and these notes stop there as well.
Complete original English transcript (15:08:02–15:08:50)
[15:08:02] [EN] So the DOM API is actually pretty simple.
[15:08:05] [EN] has some limited set of methods that we can utilize
[15:08:07] [EN] and it's very useful if we're trying
[15:08:11] [EN] to build some low-level things but
[15:08:14] [EN] But we need to think about the carrots that the
[15:08:16] [EN] DOM API has served
[15:08:20] [EN] For instance, make sure that you utilize the HTML
[15:08:23] [EN] HTML life collection wisely, so don't
[15:08:26] [EN] query too many elements and then don't try to
[15:08:29] [EN] to read the HTML live collection because
[15:08:32] [EN] because it will result to quadratic time.
[15:08:35] [EN] For most of the cases, the query selector
[15:08:38] [EN] lecture has a good performance and you can just use
[15:08:41] [EN] utilize it almost everywhere.
[15:08:44] [EN] So for rare cases when you really need to have the life
[15:08:47] [EN] collection, make sure that you work with a small subset of
[15:08:50] [EN] set of data to reduce any performance impact.