load vs DOMContentLoaded event
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).
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
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.
// 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.
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.
// 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';
Timing Comparison
| Aspect | DOMContentLoaded | load |
|---|---|---|
| Fires on | document | window |
| When | HTML parsed, DOM ready | All resources fully loaded |
| Images loaded? | No — may still be downloading | Yes — all loaded |
| Stylesheets applied? | Partially (blocks rendering, not parsing) | Yes — all applied |
| Typical delay | Fast (hundreds of ms) | Slow (seconds on heavy pages) |
| Use for | DOM manipulation, event binding, app init | Image dimensions, layout measurements, load metrics |
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).
<!-- Regular: blocks parsing → delays 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.
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.
// 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();
}
});
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)