Common HTML Interview Questions

Difficulty: Advanced

HTML interview questions test your understanding of the language that forms the foundation of every web page. While HTML may seem simple on the surface, interviewers use it to assess depth of knowledge about web standards, browser behavior, accessibility, and performance. The questions range from basic concepts like the difference between inline and block elements to advanced topics like content security policies and the browser rendering pipeline. Solid HTML knowledge signals that a candidate understands the web platform at a fundamental level.

One of the most common questions is explaining the difference between inline and block-level elements. Block elements (div, p, h1-h6, section, article) start on a new line and take up the full available width. Inline elements (span, a, strong, em, img) flow within text and only take up as much width as their content needs. Inline-block elements combine both behaviors: they flow inline but accept width, height, padding, and margin on all sides. Understanding the default display behavior and how to change it with CSS is fundamental.

Another frequently asked topic is the difference between the <script> tag's default behavior, defer, and async attributes. A regular script tag blocks HTML parsing while it downloads and executes. The defer attribute downloads the script in parallel with parsing but delays execution until the HTML is fully parsed, maintaining script order. The async attribute downloads in parallel and executes as soon as the download completes, regardless of parsing state or other script order. Knowing when to use each and their impact on page load is critical.

Data attributes (data-*) are custom attributes that let you store extra information on HTML elements without affecting rendering or semantics. They are accessed in JavaScript via element.dataset and in CSS via attr(). Common uses include storing configuration values, element state, analytics identifiers, and framework-specific data. The key rules are: names must be lowercase and at least one character after the data- prefix, values are always strings, and they should not store data that needs to be accessible to assistive technology.

The difference between localStorage, sessionStorage, and cookies is another staple question. localStorage persists until explicitly cleared and has a ~5MB limit per origin. sessionStorage persists only for the browser tab session and is cleared when the tab closes. Cookies have a 4KB limit, are sent with every HTTP request to the matching domain, support expiration dates, and can be marked HttpOnly (inaccessible to JavaScript) and Secure (HTTPS only). Each has distinct use cases: localStorage for user preferences, sessionStorage for form data during a session, cookies for authentication tokens and server-readable state.

Code examples

Inline vs Block vs Inline-Block elements

<!-- Block elements: full width, new line -->
<div style="background: lightblue; padding: 10px;">
  This div takes full width
</div>
<p style="background: lightgreen; padding: 10px;">
  This paragraph also takes full width
</p>

<!-- Inline elements: flow with text -->
<p>
  This is <span style="background: yellow;">inline span</span> and
  <a href="#" style="background: pink;">inline link</a> and
  <strong style="background: orange;">inline strong</strong>.
</p>

<!-- Inline-block: inline flow + block sizing -->
<span style="display: inline-block; width: 150px; height: 50px;
  background: coral; vertical-align: middle;">
  Inline-block box
</span>
<span style="display: inline-block; width: 150px; height: 80px;
  background: skyblue; vertical-align: middle;">
  Another inline-block
</span>

<!-- Key differences summary:
  Block:        new line, full width, respects all box model properties
  Inline:       same line, content width, ignores width/height/vertical margin
  Inline-block: same line, respects all box model properties
-->

Block elements create a new line and expand to fill their container. Inline elements flow within text and ignore width/height/vertical margin settings. Inline-block elements combine inline flow with full box model support, making them useful for creating side-by-side boxes without flexbox.

Script loading: default vs defer vs async

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Script Loading Comparison</title>

  <!-- 1. Default: blocks parsing during download AND execution -->
  <script src="blocking.js"></script>
  <!-- Parser stops -> download -> execute -> parser resumes -->

  <!-- 2. Defer: downloads in parallel, executes after parsing -->
  <script src="deferred-1.js" defer></script>
  <script src="deferred-2.js" defer></script>
  <!-- Downloads during parsing, executes in ORDER after DOMContentLoaded -->

  <!-- 3. Async: downloads in parallel, executes immediately when ready -->
  <script src="async-1.js" async></script>
  <script src="async-2.js" async></script>
  <!-- Downloads during parsing, executes whenever ready (NO order guarantee) -->
</head>
<body>
  <h1>Content</h1>

  <!--
    Timeline comparison:

    DEFAULT:  [===download===][==execute==].............[parse HTML]
    DEFER:    [===download===].............[parse HTML][==execute==]
    ASYNC:    [===download===][==execute==][parse HTML continues...]

    Use default: rarely (legacy scripts using document.write)
    Use defer:   most scripts (app bundle, libraries)
    Use async:   independent scripts (analytics, ads)
  -->
</body>
</html>

Default scripts block both parsing and rendering. Defer scripts download in parallel and execute in order after the DOM is ready, making them ideal for application code. Async scripts download in parallel but execute immediately when ready with no order guarantee, suitable for independent third-party scripts.

Data attributes and their usage

<!-- Store custom data on elements -->
<article
  data-post-id="42"
  data-author="jane-doe"
  data-published="2026-03-01"
  data-category="technology"
>
  <h2>Article Title</h2>
  <p>Article content...</p>
</article>

<button data-action="delete" data-target-id="42">
  Delete Post
</button>

<script>
  // Access via dataset (camelCase conversion)
  const article = document.querySelector('article');
  console.log(article.dataset.postId);     // "42"
  console.log(article.dataset.author);     // "jane-doe"
  console.log(article.dataset.published);  // "2026-03-01"

  // Set data attributes
  article.dataset.views = '1500';
  // Creates: data-views="1500"

  // Event delegation with data attributes
  document.addEventListener('click', (e) => {
    const button = e.target.closest('[data-action]');
    if (!button) return;

    const action = button.dataset.action;     // "delete"
    const targetId = button.dataset.targetId; // "42"
    console.log(`Action: ${action}, Target: ${targetId}`);
  });
</script>

<!-- CSS can select by data attribute -->
<style>
  [data-category="technology"] {
    border-left: 4px solid blue;
  }
  [data-action="delete"] {
    color: red;
  }
</style>

Data attributes store custom metadata on HTML elements. JavaScript accesses them via element.dataset with automatic hyphen-to-camelCase conversion (data-post-id becomes dataset.postId). CSS can select elements by data attributes using attribute selectors. Values are always strings.

localStorage vs sessionStorage vs cookies

<script>
  // --- localStorage ---
  // Persists until explicitly cleared. ~5MB per origin.
  localStorage.setItem('theme', 'dark');
  localStorage.setItem('user', JSON.stringify({ name: 'Jane', id: 42 }));

  const theme = localStorage.getItem('theme'); // 'dark'
  const user = JSON.parse(localStorage.getItem('user'));
  console.log(user.name); // 'Jane'

  localStorage.removeItem('theme');
  // localStorage.clear(); // removes everything

  // --- sessionStorage ---
  // Persists only for the current tab/session. ~5MB per origin.
  sessionStorage.setItem('formDraft', JSON.stringify({
    title: 'My Post',
    content: 'Draft content...'
  }));

  // Survives page refresh, cleared on tab close
  const draft = JSON.parse(sessionStorage.getItem('formDraft'));

  // --- Cookies ---
  // Sent with every HTTP request. 4KB limit.
  document.cookie = 'language=en; path=/; max-age=31536000'; // 1 year
  document.cookie = 'session=abc123; path=/; secure; samesite=strict';

  // Read all cookies (returns single string)
  console.log(document.cookie);
  // "language=en; session=abc123"

  // Parse cookies into object
  function getCookies() {
    return document.cookie.split('; ').reduce((acc, pair) => {
      const [key, value] = pair.split('=');
      acc[key] = decodeURIComponent(value);
      return acc;
    }, {});
  }

  console.log(getCookies().language); // 'en'

  /*
    Comparison:
    Feature       | localStorage | sessionStorage | Cookies
    Capacity      | ~5MB         | ~5MB           | ~4KB
    Persistence   | Forever      | Tab session    | Configurable
    Sent to server| No           | No             | Yes (every request)
    Accessible JS | Yes          | Yes            | Yes (unless HttpOnly)
    Scope         | Origin       | Origin + Tab   | Origin + Path
  */
</script>

localStorage persists across browser sessions and tabs. sessionStorage is scoped to a single tab and cleared on close. Cookies are small, sent to the server with every request, and support features like HttpOnly (no JS access), Secure (HTTPS only), and SameSite (CSRF protection). Choose based on persistence needs, size, and whether the server needs access.

Key points

Concepts covered

Interview Preparation, HTML Fundamentals, Semantic HTML, Accessibility, SEO, Web Standards