Browser & DOMPerformanceMedium

load vs DOMContentLoaded event

01

The Short Answer

DOMContentLoaded fires when the HTML has been fully parsed and the DOM tree is built — but external resources like images, stylesheets, and iframes may still be loading. The load event fires later, when everything on the page has finished loading (images, fonts, stylesheets, iframes — all of it). Use DOMContentLoaded for DOM manipulation and JS initialization; use load only when you need fully loaded resources (like image dimensions).

02

The Page Loading Timeline

Understanding when each event fires requires knowing the browser's loading sequence. The HTML is parsed top-to-bottom, building the DOM as it goes. Once parsing completes, DOMContentLoaded fires. Then the browser continues loading external resources, and only when everything is done does load fire.

  • Browser starts receiving HTML bytes
  • Parser builds DOM tree incrementally (top to bottom)
  • Parser encounters <script> tags — blocks parsing until script executes (unless async/defer)
  • HTML fully parsed → DOM tree complete → DOMContentLoaded fires
  • Images, fonts, stylesheets, iframes continue loading in background
  • ALL resources finished → load event fires on window
03

DOMContentLoaded

This event fires on the document object when the HTML parsing is complete and the full DOM tree is available. At this point, you can safely query and manipulate any DOM element. However, images might still be loading (so their dimensions might be 0), and external stylesheets might not have been applied yet.

dom-content-loaded.tstypescript
// Fires when DOM is ready — images/styles may still be loading
document.addEventListener('DOMContentLoaded', () => {
  // ✅ Safe to query DOM elements
  const header = document.getElementById('header');
  const buttons = document.querySelectorAll('.btn');

  // ✅ Safe to attach event listeners
  buttons.forEach((btn) => btn.addEventListener('click', handleClick));

  // ✅ Safe to initialize JS frameworks/components
  initializeApp();

  // ⚠️ Images might not be loaded yet
  const img = document.querySelector('img');
  console.log(img?.naturalWidth); // Might be 0!
});

// Equivalent in jQuery:
// $(document).ready(function() { ... });
// or the shorthand: $(function() { ... });

This is the event you want for 90% of initialization code. It fires as early as possible while guaranteeing the DOM is complete. Waiting for load would unnecessarily delay your JavaScript initialization.

04

The load Event

The load event fires on window when the entire page is fully loaded — every image has been downloaded, every stylesheet applied, every iframe loaded. This can be significantly later than DOMContentLoaded, especially on image-heavy pages or pages with large external resources.

load-event.tstypescript
// Fires when EVERYTHING is loaded (images, styles, iframes, etc.)
window.addEventListener('load', () => {
  // ✅ Images are fully loaded — dimensions are accurate
  const img = document.querySelector('img')!;
  console.log(img.naturalWidth, img.naturalHeight); // Correct values

  // ✅ Stylesheets are applied — computed styles are accurate
  const el = document.querySelector('.hero')!;
  const height = getComputedStyle(el).height; // Reliable

  // ✅ Use for:
  // - Image galleries that need dimensions
  // - Layout calculations that depend on loaded assets
  // - Removing loading spinners/skeletons
  // - Performance measurements (page fully ready)
  hideLoadingSpinner();
  measurePageLoadTime();
});

// Individual resource load events
const img = new Image();
img.addEventListener('load', () => {
  console.log('This specific image loaded:', img.width, img.height);
});
img.src = '/hero-image.jpg';
05

Timing Comparison

AspectDOMContentLoadedload
Fires ondocumentwindow
WhenHTML parsed, DOM readyAll resources fully loaded
Images loaded?No — may still be downloadingYes — all loaded
Stylesheets applied?Partially (blocks rendering, not parsing)Yes — all applied
Typical delayFast (hundreds of ms)Slow (seconds on heavy pages)
Use forDOM manipulation, event binding, app initImage dimensions, layout measurements, load metrics
06

How Scripts Affect DOMContentLoaded

Regular <script> tags block HTML parsing — the parser stops, downloads the script, executes it, then continues parsing. This delays DOMContentLoaded. Scripts with defer download in parallel but execute after parsing (just before DOMContentLoaded). Scripts with async download in parallel and execute immediately when ready (may fire before or after DOMContentLoaded).

script-loading.htmlhtml
<!-- Regular: blocks parsingdelays DOMContentLoaded -->
<script src="app.js"></script>

<!-- defer: downloads in parallel, executes BEFORE DOMContentLoaded -->
<!-- Guaranteed execution order, guaranteed DOM is ready -->
<script defer src="app.js"></script>

<!-- async: downloads in parallel, executes ASAP (unpredictable timing) -->
<!-- May run before or after DOMContentLoaded -->
<script async src="analytics.js"></script>

defer is usually what you want

Place scripts with defer in the <head>. They download in parallel with HTML parsing (no blocking), execute in order after parsing completes, and the DOM is guaranteed to be ready. It's the best of both worlds — fast loading and reliable DOM access.

07

The beforeunload and unload Events

For completeness, there are also events for when the user leaves the page. beforeunload fires before navigation and can show a confirmation dialog (for unsaved changes). unload fires as the page is being torn down — but it's unreliable and blocks the browser's back-forward cache. Prefer visibilitychange for cleanup.

page-lifecycle.tstypescript
// Full page lifecycle:
// DOMContentLoaded → load → (user interacts) → beforeunload → unload

// Warn about unsaved changes
window.addEventListener('beforeunload', (event) => {
  if (hasUnsavedChanges) {
    event.preventDefault(); // Triggers browser's "Leave page?" dialog
  }
});

// Better for analytics/cleanup — doesn't block bfcache
document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') {
    // User navigated away or switched tabs
    sendAnalyticsBeacon();
  }
});
08

Why Interviewers Ask This

This question tests your understanding of the browser's page loading pipeline. Interviewers want to see that you know the difference between DOM readiness and full page load, understand how script loading strategies (defer, async) affect timing, can choose the right event for different initialization needs, and are aware of performance implications. It's practical knowledge that affects every web application's startup behavior.

Quick Revision Cheat Sheet

DOMContentLoaded: DOM tree ready, external resources may still load — use for JS init

load: Everything loaded (images, styles, iframes) — use for dimensions/metrics

defer scripts: Execute after parsing, before DOMContentLoaded — best default

async scripts: Execute ASAP — unpredictable timing relative to DOM

jQuery equivalent: $(document).ready() ≈ DOMContentLoaded

Page exit: Use visibilitychange over unload (doesn't block bfcache)