skip to content

A templating partial ships a `<div>` whose end tag is missing. HTML has no fatal parse errors — what does the browser do with the markup that follows, and how would you catch this class of bug before release?

level: seniorimportance: should knowfreq 40%

answer

  1. no exceptions, only prescribed repair
  2. the stack of open elements
  3. later markup becomes descendants
  4. source and built tree disagree
  5. validate rendered pages in CI

basics

~20 s

Nothing fails. The div stays on the parser's stack of open elements, so following markup becomes its descendants until an ancestor's end tag or end of file closes it implicitly. Catch it by validating rendered pages in CI, not by eye.

solid answer

~50 s

The HTML parser has no exceptions to throw. An unclosed `<div>` simply stays on the stack of open elements, so every element parsed after it is inserted as its descendant. It closes when some ancestor's end tag — `</section>`, `</main>`, `</body>` — pops through it, or at end of file, each pop recording a parse error the page never sees. The symptom is therefore not a blank screen but a subtree nested one level deeper than the author drew: scoped styles reach further than intended, structure-sensitive queries miss, and the accessibility tree changes shape. Because the recovery is fully specified by the HTML Living Standard, every modern engine builds the *same* wrong tree, so "it works in my browser" proves nothing. The reliable catch is an HTML validator such as the W3C Nu Html Checker or a linter like html-validate run over the **rendered** pages in CI, since per-file template linting cannot see a tag opened in one partial and closed in another.

code

html · 6 lines
html
<section>
  <div class="card">
    <h2>Card</h2>
  <p>Meant to be outside the card.</p>
</section>
<footer>Meant to be outside the section.</footer>

go deeper

for a junior

Know that HTML never fails to parse: a missing end tag means the following content becomes nested inside, with no error shown anywhere.

for a middle

Explain the stack of open elements and when the unclosed element is finally popped — an ancestor's end tag or end of file — and why the built tree ends up deeper than the source.

for a senior

Show the diagnosis and the fix: compare source against the built tree, know that recovery is specified so cross-browser testing proves nothing, and validate rendered pages in CI.

for a principal

Own the prevention strategy — self-contained partials, compiled templates over string concatenation, and where structural validation sits in the pipeline so this class of defect cannot reach production.

## Why HTML never throws XML chose draconian error handling: a well-formedness error means the document does not render. HTML chose the opposite, and then — this is the important half — *specified* the recovery. The HTML Living Standard defines the tokenizer, the tree-construction insertion modes, and a named parse error for every malformed situation, together with exactly what the parser must do next. "Parse error" in HTML means "non-conforming input, handled in this prescribed way", never "stop". The consequence people misremember is that browsers guess. They do not, not since HTML5 unified this: two modern engines fed identical broken bytes build identical DOMs. That is good news for interoperability and bad news for detection, because there is no divergence to alert you. ## What actually happens to the following markup Tree construction maintains a **stack of open elements**. `<div>` pushes; `</div>` pops. With no end tag, the div sits on the stack and every subsequently inserted element becomes a child of the current node — which is the div, or something below it. ```html <section> <div class="card"> <h2>Card</h2> <p>Intended as a sibling of the card.</p> </section> <footer>Intended as a sibling of the section.</footer> ``` The paragraph lands inside `.card`. Then `</section>` arrives: the rule for that end tag checks whether a matching element is in scope, generates implied end tags, and — noting that the current node is not a `section` — records a parse error and pops elements until the section itself has been popped. The div is closed as collateral. The footer, arriving after the section is gone, sits where it was meant to. At end of file the same thing happens wholesale: whatever remains on the stack is closed, silently. ## Why the symptom is confusing The served bytes and the built tree disagree, and almost every debugging habit starts from the bytes. Someone reads the template, sees balanced-looking tags across two files, and concludes the problem must be styling. Meanwhile: - a rule scoped to `.card` reaches content that was never meant to be in the card; - a structure-dependent query returns the wrong node or none; - landmark and heading structure shifts, so assistive technology announces a different document than the one drawn; - the layout looks *nearly* right, which is why it shipped. The fastest triage step is to compare the served source with the built element tree. If they differ in nesting depth, stop looking at styles. ## Adjacent recoveries that make it worse Unclosed tags interact with the other repair rules covered by this parsing model: - Inside a table, misplaced content is **foster parented** out of the table entirely, so an imbalance can move a block of content above the table. - A `<p>` is auto-closed by the next block-level start tag, so an unclosed paragraph rarely swallows anything — but a stray `</p>` materialises an empty one. - Misnested **formatting elements** (`b`, `i`, `em`, `strong`, `a`, `font` and friends) are handled by the *adoption agency algorithm*, which reconstructs them across the mismatch. `<b>one<i>two</b>three</i>` yields a bold element and then an italic element that the parser has re-created to carry the bold formatting — duplicated elements you never wrote. ## Catching it before release 1. **Validate rendered output, not templates.** Run an HTML validator — the W3C Nu Html Checker, or a linter such as html-validate — over the actual HTML produced for a set of representative routes, and fail the build on structural errors. This is the only layer that sees a tag opened in one partial and closed in another. 2. **Prefer compiled templates where you can.** A template language that is parsed and compiled rejects unbalanced tags at build time; string concatenation of markup fragments cannot, and that is exactly where these bugs are born. 3. **Make partials self-contained.** A house rule that no partial may open an element it does not close removes the whole class. Where a wrapper genuinely spans partials, express it as one component that renders its children rather than as an open tag and a close tag in separate files. 4. **Snapshot the structure, not the pixels.** A structural snapshot of key pages fails loudly when nesting depth changes, long before anyone notices visually. 5. **Do not rely on the console.** Parse errors are not reported to page script. Nothing will tell you at runtime. ## The interview answer State the mechanism (stack of open elements, implied closing at an ancestor end tag or EOF), state that recovery is specified so all engines agree, name the real-world symptom (DOM deeper than source, no error anywhere), and finish with the pipeline answer: validate the rendered HTML in CI, because file-level linting structurally cannot catch a split tag.

  • Are two modern browsers guaranteed to build the same DOM from the same malformed markup?
    Yes, modulo engine bugs. The HTML Living Standard specifies the tokenizer, every insertion mode, and the recovery for each named parse error, so error handling is interoperable by design. That was one of HTML5's central goals, replacing the pre-HTML5 situation where each engine reverse-engineered the others. It also means "it renders fine here" is no evidence at all.
  • How does an unclosed tag behave differently inside a table?
    The in-table insertion mode foster-parents content that has no legal place, inserting it immediately before the table element rather than inside it. So an imbalance in generated row markup can relocate a whole block of content above the table in the tree. The content is never dropped — it moves, which is why the symptom looks like a layout bug.
  • Why will linting each template file in isolation not catch this?
    Because the tag is balanced across files, not within one. A header partial opens a wrapper and a footer partial closes it, so each file is individually unbalanced by design and the linter either complains constantly or is configured to stay quiet. Only the composed, rendered document shows the real structure, which is why validation belongs after rendering.
  • What happens to misnested formatting elements such as `<b>one<i>two</b>three</i>`?
    The adoption agency algorithm reconstructs them. The parser closes the bold element at `</b>`, then re-creates a bold element inside the still-open italic so the remaining text keeps its formatting. You end up with more elements than you wrote, in a shape that is specified and identical across engines but rarely the one the author intended.

saying these in an interview costs you the question

  • Expects a console error or a failed page load
  • Says each browser guesses differently at recovery
  • Trusts view source instead of the built element tree
  • Assumes per-file template linting catches split tags
  • Treats the deeper nesting as a CSS problem

context