
DOMContentLoaded vs. load in JavaScript

This Article is Over Eleven Years Old...
Things can and do move very quickly in tech, which means that tech-related articles go out of date almost as soon as they have been written and published. If you are looking for up-to-date technical advice or opinion, it is unlikely that you will find it on this page.
You may find that my recent articles are more relevant, and you are always welcome to drop me a line if you have a specific technical problem you are trying to solve.
One of the first timing problems new JavaScript developers hit is painfully ordinary: the code runs, the selector looks right, and yet the element is missing because the page is not ready when the script executes.
That usually sends people looking for some vague "run this later" solution, and they quickly stumble into two browser events: DOMContentLoaded and load.
At first glance they sound interchangeable. They are not.
The difference matters because they fire at different points in the browser's page‑loading process, and using the wrong one can make your code run either too early or much later than necessary.
DOMContentLoaded fires when the HTML has been parsed
DOMContentLoaded fires when the browser has finished parsing the HTML document and built the DOM tree.
That means:
- the document structure exists
- elements can be queried
- you can attach event listeners
- you can update text, classes, and attributes
It does not mean every external resource has finished loading.
Images, stylesheets, iframes, and other assets may still be loading when DOMContentLoaded fires. The key point is that the document structure is ready to work with.
That makes it the right event for front‑end behaviour surprisingly often.
document.addEventListener('DOMContentLoaded', () => { const button = document.querySelector<HTMLButtonElement>('.save-button'); if (!button) { return; } button.addEventListener('click', () => { console.log('Saved'); });});If your goal is "wait until the elements exist so I can wire up behaviour", DOMContentLoaded is usually enough.
load waits for much more
The load event fires later.
It waits for the whole page to finish loading, including dependent resources such as:
- images
- stylesheets
- iframes
- scripts loaded in the normal way
That means load is a heavier milestone. If you only need the DOM, waiting for load can be unnecessary.
window.addEventListener('load', () => { console.log('Everything is loaded');});That can be useful in some cases, but it is not the best default for ordinary UI setup.
Why New Developers Confuse Them
The confusion usually comes from thinking of page load as one single moment.
It is not. The browser moves through stages:
- it downloads and parses HTML
- it builds the DOM
- it continues loading other resources
- it eventually reaches a fully loaded state
DOMContentLoaded lives closer to stage two.
load lives closer to stage four.
If you are wiring up buttons, menus, tabs, forms, or text changes, stage two is normally enough. Waiting all the way to stage four can make the page feel more sluggish for no good reason.
A Practical Example
Imagine a page containing this markup:
<button class="menu-toggle">Menu</button><img src="/hero.jpg" alt="" />If your goal is simply to attach a click handler to the button, you do not need to wait for the image to download first.
document.addEventListener('DOMContentLoaded', () => { const toggle = document.querySelector<HTMLButtonElement>('.menu-toggle'); toggle?.addEventListener('click', () => { console.log('Open menu'); });});Using load there would still work, but it would delay the setup until after the image and other resources had finished loading. That is later than necessary.
When load is actually the better choice
load makes more sense when your code depends on resources beyond the raw DOM structure.
For example:
- measuring image dimensions after the image has loaded
- running logic that depends on all styles being applied and assets available
- coordinating with iframes or resource‑heavy widgets
If you need to know the full page is ready, not just the DOM tree, load is the more honest event.
So the question should not be "which event is best?" It should be "what exactly am I waiting for?"
Script Placement Also Changes the Picture
Before reaching for either event, it is worth remembering that script placement matters too.
If your script tag sits at the end of the body, the browser has already parsed the earlier HTML by the time the script runs.
<body> <button class="menu-toggle">Menu</button> <script src="/app.js"></script></body>In setups like that, your code may be able to query the DOM immediately without wrapping everything in DOMContentLoaded.
Likewise, a script with the defer attribute waits until after the document has been parsed before it executes:
<script src="/app.js" defer></script>That means some older "wrap absolutely everything in DOMContentLoaded" advice becomes less necessary once your script loading strategy is sensible.
DOMContentLoaded is about structure, not visual completeness
This is another useful distinction.
If a page has fired DOMContentLoaded, it does not mean the page looks complete yet. It means the document exists and can be manipulated.
That is a much more useful milestone for JavaScript than "everything the user can eventually see is fully downloaded". Many front‑end behaviours only care that the structure is available.
Beginners sometimes wait for load because it feels safer. In reality, that often just hides a fuzzy mental model of what the code actually depends on.
Browser Timing Bugs Often Come from Using the Wrong Milestone
If your code is failing because elements are missing, your problem is often that you are running before the DOM is ready.
If your code is running much later than you expect, your problem may be that you are waiting for load when only DOMContentLoaded was needed.
Both events are worth understanding properly. They are both valid. They just answer different timing questions.
A Useful Rule of Thumb
For ordinary front‑end development:
- use
DOMContentLoadedwhen you need the DOM tree - use
loadwhen you need all dependent resources too
That simple rule is not perfect in every edge case, but it is reliable enough for most day‑to‑day work.
Wrapping up
DOMContentLoaded and load are not competing names for the same thing. DOMContentLoaded fires when the HTML has been parsed and the DOM is ready. load fires later, once the broader page resources have finished loading too. Most UI behaviour only needs the former, whilst a smaller set of cases genuinely depends on the latter.
Key Takeaways
DOMContentLoadedfires when the DOM has been built from the HTML.loadfires later, after additional resources such as images and iframes finish loading.- Most JavaScript UI setup only needs
DOMContentLoaded. - Script placement and
defercan remove the need for extra readiness wrappers in some cases. - The right event depends on what your code is truly waiting for.
Once you stop treating page readiness as one vague moment, these events become much easier to choose between.
Related Articles

Why querySelector Returns null in JavaScript. Why

Understanding Transient Props in styled‑components. Understanding Transient Props in
styled‑components
Disabling Text Selection Highlighting with CSS. Disabling Text Selection Highlighting with CSS

Converting Between Camel, Snake, and Kebab Case in JavaScript. Converting Between Camel, Snake, and Kebab Case in JavaScript

Exploring the Liquid Templating Language. Exploring the Liquid Templating Language

Using Middleware in Next.js for Route Protection. Using Middleware in Next.js for Route Protection

Margin Collapse in CSS. Margin Collapse in CSS

Leveraging JavaScript Frameworks for Efficient Development. Leveraging JavaScript Frameworks for Efficient Development

Reverse an Array in JavaScript. Reverse an Array in JavaScript

Check If Today is Between Two Dates in JavaScript. Check If Today is Between Two Dates in JavaScript

Rethinking Carousels: Going Around in Circles. Rethinking Carousels: Going Around in Circles

Track Element Visibility Using Intersection Observer. Track Element Visibility Using Intersection Observer