skip to content

Given `<div id="box">old</div>`, where do insertAdjacentHTML's four position strings put the new markup, and why would you use it rather than `box.innerHTML += markup`?

level: middleimportance: should knowfreq 45%

answer

  1. four keywords, two inside and two outside
  2. begin and end refer to the element's tags
  3. beforeend is the append case
  4. outside positions need a parent
  5. only the new string is parsed

basics

~10 s

The four positions are beforebegin (previous sibling), afterbegin (first child), beforeend (last child) and afterend (next sibling). Unlike appending to innerHTML, it parses only the new string and leaves the element's existing nodes untouched.

solid answer

~50 s

`box.insertAdjacentHTML(position, markup)` parses the string as HTML and inserts the result at one of four points relative to the element: `'beforebegin'` puts it just before `box` as a previous sibling, `'afterbegin'` as `box`'s first child, `'beforeend'` as `box`'s last child, and `'afterend'` just after `box` as a next sibling. The mnemonic is that begin and end refer to `box`'s own tags. `'beforebegin'` and `'afterend'` need a parent to insert into, so calling them on a detached element throws a `NoModificationAllowedError`. The reason to prefer it over `innerHTML +=` is that `+=` reads the current children back out as a string, concatenates, and reparses the whole thing, destroying and rebuilding every existing node — losing their listeners, focus and selection — while `insertAdjacentHTML` parses only the new fragment and leaves existing nodes alone. It is still an HTML parsing sink, so only pass markup you construct yourself.

code

javascript · 18 lines
javascript
document.body.innerHTML = '<div id="box">old</div>';
const box = document.getElementById('box');

box.insertAdjacentHTML('beforebegin', '<p>A</p>'); // previous sibling
box.insertAdjacentHTML('afterbegin',  '<p>B</p>'); // first child
box.insertAdjacentHTML('beforeend',   '<p>C</p>'); // last child
box.insertAdjacentHTML('afterend',    '<p>D</p>'); // next sibling

console.log(document.body.innerHTML);
// <p>A</p><div id="box"><p>B</p>old<p>C</p></div><p>D</p>

const detached = document.createElement('div');
detached.insertAdjacentHTML('beforeend', 'ok');    // fine, inserts inside
try {
  detached.insertAdjacentHTML('beforebegin', 'no'); // needs a parent
} catch (e) {
  console.log(e.name); // "NoModificationAllowedError"
}

go deeper

for a junior

Memorise the four keywords and that begin and end refer to the element's own tags, so afterbegin is first child and beforeend is last child. Know beforeend is the everyday append.

for a middle

Explain the read-concatenate-reparse cycle behind innerHTML += and why that destroys existing child nodes, and say which two positions require a parent and what they throw without one.

for a senior

Show the decision: nodes go in with insertAdjacentElement or append, text with insertAdjacentText, and only assembled markup strings justify the parser. Name the concrete losses from reparsing — listeners, focus, selection, iframe state.

for a principal

Own the policy question: string-built HTML scattered through a codebase is both a maintenance and a review-surface problem, so decide where markup construction is allowed to live and what enforcement makes that stick.

## The four positions `insertAdjacentHTML(position, text)` takes a case-insensitive position keyword and a markup string. The keywords are easiest to remember by picturing the element's own start and end tags: ```html <!-- beforebegin --> <div id="box"> <!-- afterbegin --> old <!-- beforeend --> </div> <!-- afterend --> ``` - `'beforebegin'` — inserted before `box`, as its previous sibling. - `'afterbegin'` — inserted inside `box`, before its first child. - `'beforeend'` — inserted inside `box`, after its last child. This is the append case and the one you use most. - `'afterend'` — inserted after `box`, as its next sibling. A useful check: `afterbegin` and `beforeend` land *inside*; `beforebegin` and `afterend` land *outside*. Any other string throws a `SyntaxError` `DOMException`, so a typo fails loudly rather than silently doing the wrong thing. ## When it throws The outside positions need somewhere to put the nodes. If the element has no parent — you created it with `createElement` and have not inserted it yet — `'beforebegin'` and `'afterend'` throw a `NoModificationAllowedError` `DOMException`. The inside positions work fine on a detached element, since the element itself is the insertion point. ## Why not innerHTML += `box.innerHTML += markup` looks like an append but is really three operations: 1. **Read.** The browser serializes every existing child of `box` back into an HTML string. 2. **Concatenate.** That string plus your new markup. 3. **Write.** `box`'s children are all removed and the combined string is parsed from scratch. Every existing node is therefore destroyed and replaced by a freshly parsed lookalike. The visible consequences are concrete: listeners attached with `addEventListener` to the old children are gone, an input the user was typing in loses focus and its typed value, a selection collapses, an `<iframe>` inside reloads, and any JavaScript reference you were holding to a child now points at a detached orphan. The work is also proportional to the *whole* container each time, so appending N items costs quadratically. `insertAdjacentHTML` parses only the string you passed and inserts the resulting nodes at one point. Existing children are never touched — same objects, same listeners, same focus. ```js // destroys and rebuilds every row list.innerHTML += '<li>new</li>'; // parses one <li> and appends it list.insertAdjacentHTML('beforeend', '<li>new</li>'); ``` ## The sibling methods Two companions take the same position keywords and are frequently the better choice: - `insertAdjacentElement(position, element)` — inserts an existing element node, no parsing involved, and returns the inserted element. - `insertAdjacentText(position, data)` — inserts a string as a text node, never as markup. If you already have a node, `insertAdjacentElement` is strictly better than serializing it to a string and reparsing it. And when the content is plain text, `insertAdjacentText` avoids the parser entirely. ## It is a parsing sink `insertAdjacentHTML` interprets its argument as HTML, so a string containing markup becomes real elements. Only pass strings you assemble yourself from safe pieces; treating it as a way to display user-supplied content is a category error. The same care applies to any API that turns a string into nodes. ## Choosing an insertion API A workable decision order: 1. Already have nodes? Use `append`, `prepend`, `before`, `after` or `insertAdjacentElement`. 2. Inserting plain text? Use `append(string)` or `insertAdjacentText` — no parser, no escaping questions. 3. Have a markup string you built and need it parsed in place? `insertAdjacentHTML` with the right position keyword. 4. Replacing an entire container's contents wholesale? That is a different operation with different tradeoffs, and it is the one that throws away all existing children by design.

  • Which of the four positions can you use on an element you created but have not inserted yet?
    Only `'afterbegin'` and `'beforeend'`, the two that insert inside the element. `'beforebegin'` and `'afterend'` need the element's parent as the insertion point, and when that parent is null the call throws a `NoModificationAllowedError` `DOMException`. Insert the element first, then use the outside positions.
  • You already have an element node in a variable. Is insertAdjacentHTML still the right call?
    No — use `insertAdjacentElement(position, node)`, which takes the same four keywords and inserts the node directly. Serializing a node to a string just so the parser can rebuild it wastes work, loses the node's listeners and expando properties, and reintroduces escaping concerns for no benefit.
  • Why does appending with innerHTML += get slower as a list grows?
    Because each `+=` serializes every existing child back to a string, concatenates, and reparses the whole container. The work per append is proportional to the current size, so N appends cost on the order of N squared. `insertAdjacentHTML('beforeend', …)` parses only the new markup, so each append costs the same regardless of list length.

saying these in an interview costs you the question

  • Mixing up afterbegin with beforebegin
  • Thinking innerHTML += only appends the new markup
  • Expecting listeners on existing children to survive innerHTML +=
  • Using beforebegin on an element that has no parent
  • Passing unsanitised user input as the markup string

context