In the browser DOM, how does parent.append() differ from parent.appendChild(), and what do the newer methods prepend, before, after, replaceWith and remove let you do that the older Node methods cannot?
answer
- one node versus many arguments
- strings become text, not markup
- return value differs between the two
- ChildNode methods need no parent variable
- el.remove() versus parentNode.removeChild(el)
basics
~20 sappend() accepts any number of nodes or strings, turns strings into text nodes, and returns undefined; appendChild() takes exactly one Node and returns it. prepend, before, after, replaceWith and remove share append()'s flexibility and act relative to the element itself, so no parent reference is needed.
solid answer
~40 s`appendChild()` is the old `Node` API: one `Node` argument, returns that node, throws a `TypeError` if you hand it a string, and you must already hold the parent. `append()` comes from the `ParentNode` mixin: it is variadic, accepts strings as well as nodes (a string becomes a `Text` node, it is never parsed as HTML), and returns `undefined`, so it cannot be chained. The `ChildNode` mixin adds `before()`, `after()`, `replaceWith()` and `remove()`, which act relative to the element itself — `el.remove()` replaces `el.parentNode.removeChild(el)`, and `el.before(node)` replaces `parent.insertBefore(node, el)`. `ParentNode` also gives `replaceChildren(...nodes)`, which clears the children and inserts the new ones in one call. All of these have been supported in every current browser for years; I default to the modern set and reach for `insertBefore` only in old code.
code
javascript · 15 linesconst box = document.createElement('div');
// append: variadic, strings become Text, returns undefined
const r1 = box.append(document.createElement('span'), ' and text');
console.log(r1, box.childNodes.length); // undefined 2
// appendChild: one Node, returns it
const p = box.appendChild(document.createElement('p'));
console.log(p === box.lastChild); // true
// ChildNode methods act on the element itself
p.before('before p');
p.replaceWith(document.createElement('hr'));
box.firstElementChild.remove();
console.log(box.innerHTML); // " and textbefore p<hr>"go deeper
Know that append() takes several arguments and accepts strings while appendChild() takes exactly one Node, and that el.remove() is the modern way to delete an element. Say plainly that append() returns undefined.
Explain where the two generations come from — Node methods versus the ParentNode and ChildNode mixins — and name the concrete differences: arity, string coercion to Text, return value, and acting on the child instead of the parent.
Show judgment about which to use in real code: the ChildNode methods avoid null-parent crashes, replaceChildren() clears a container in one call, and appendChild()'s return value still earns its keep. Mention that all of these move rather than copy.
Own the codebase-wide call: whether to standardise on the modern set, how you would enforce it in lint rules, and why introducing a helper layer over three-line DOM calls usually costs more than it saves.
## Two generations of insertion API The DOM grew its mutation methods in two waves, and both are still present, which is why the same job has two spellings. The original methods live on `Node`: `appendChild(node)`, `insertBefore(node, refNode)`, `replaceChild(newNode, oldNode)` and `removeChild(node)`. They share a shape: exactly one node argument, a return value that is the node acted on, and a mandatory *parent* receiver — you always call them on the parent, never on the node you care about. The modern methods come from two interface mixins that many node types implement. `ParentNode` contributes `append(...)`, `prepend(...)` and `replaceChildren(...)`. `ChildNode` contributes `before(...)`, `after(...)`, `replaceWith(...)` and `remove()`. ## What append() does that appendChild() cannot Three concrete differences: 1. **Variadic.** `list.append(a, b, c)` inserts three nodes in one call. `appendChild` needs three calls. 2. **Strings allowed.** `el.append('hello')` creates and appends a `Text` node. `el.appendChild('hello')` throws a `TypeError` because the argument is not a `Node`. Note that the string is *text*, not markup: `el.append('<b>hi</b>')` renders the literal characters `<b>hi</b>`, it does not create a bold element. 3. **Return value.** `appendChild` returns the appended node, so `const p = box.appendChild(document.createElement('p'))` works. `append` returns `undefined`, so the same pattern silently gives you `undefined` — a very common first stumble. ```js const p = document.createElement('p'); document.body.append(p, ' trailing text'); // two insertions, one call const same = document.body.appendChild(p); // same === p ``` ## The ChildNode methods remove the parent dance The old API forces you to reach upward before you can act: ```js el.parentNode.removeChild(el); // old el.parentNode.insertBefore(newEl, el); // old el.parentNode.replaceChild(newEl, el); // old ``` The `ChildNode` methods say the same thing about the element itself: ```js el.remove(); el.before(newEl); el.replaceWith(newEl); ``` Besides being shorter, these avoid a class of bug: if `el` has already been detached, `el.parentNode` is `null` and the old form throws, while `el.remove()` is a harmless no-op. `replaceWith()` and the rest are also variadic and string-accepting, so `el.replaceWith('gone')` swaps the element for a text node, and `el.replaceWith()` with no arguments simply removes it. ## prepend and replaceChildren `parent.prepend(node)` inserts at the front — the old equivalent is `parent.insertBefore(node, parent.firstChild)`, which relies on the fact that `insertBefore` with a `null` reference node appends rather than throwing. `parent.replaceChildren(...nodes)` removes every existing child and inserts the arguments, in one step. Called with no arguments it empties the element. That makes it a direct, node-oriented way to clear a container. ## Rules that apply to all of them - **They move, they do not copy.** Passing a node that already lives somewhere in the document detaches it from its old parent first. You end up with one node in the new place, not two. - **They accept a `DocumentFragment`.** Inserting a fragment inserts the fragment's children. - **Hierarchy is enforced.** Inserting an ancestor into its own descendant throws a `HierarchyRequestError` `DOMException`, because the DOM must stay a tree. - **No HTML parsing anywhere.** None of these methods interpret markup. Building elements from a string is a different API entirely. ## Browser support and what to use `append`, `prepend`, `before`, `after`, `replaceWith` and `remove` shipped across browsers in 2016 (Chrome 54, Firefox 49, Safari 10); `replaceChildren` followed in 2020 (Chrome 86, Firefox 78, Safari 14). All are safe in any project that targets current browsers, and neither set is deprecated — `appendChild` is not going away. Practical default: use the modern set for readability, and remember `appendChild`'s return value when you want the create-and-insert-in-one-expression style. Reach for `insertBefore` when you genuinely need positional insertion computed from a reference node and you like the `null`-means-append behaviour.
- Why does `const p = box.append(document.createElement('p'))` leave `p` as undefined?Because `append()` is specified to return `undefined` — it is variadic, so there is no single node it could sensibly hand back. `appendChild()` returns the node it inserted, which is what enables the create-and-capture one-liner. With `append()` you must create the element into a variable first, then append it.
- What does `el.append('<b>hi</b>')` render?The literal characters `<b>hi</b>`. Every string argument to `append`, `prepend`, `before`, `after` or `replaceWith` is converted to a `Text` node, so angle brackets are escaped on serialization rather than parsed as markup. Inserting real markup from a string requires a parsing API such as `insertAdjacentHTML`, which is a separate decision with separate safety rules.
- What happens if you call `el.remove()` on an element that is not in any tree?Nothing — it is a silent no-op. The specified algorithm checks whether the node has a parent and returns early if it does not. That is safer than the old `el.parentNode.removeChild(el)`, which throws a `TypeError` on `null` when the element has already been detached.
saying these in an interview costs you the question
- Thinking append() returns the appended node like appendChild()
- Believing a string passed to append() is parsed as HTML
- Calling appendChild() with a string and expecting a text node
- Assuming append() and appendChild() copy the node instead of moving it
- Claiming appendChild() is deprecated and must be replaced