Question
I am loading an external JavaScript file from a <script> tag inside the <head>.
Because that script runs before the page has fully loaded, I cannot reliably access elements inside the <body> and other parts of the document yet.
How can I run JavaScript only after the document has finished loading into memory? Are there browser events I can listen to so code runs when the page is ready?
Short Answer
By the end of this page, you will understand how browsers load HTML and JavaScript, why code in the <head> may run too early, and how to delay execution until the document is ready. You will learn when to use DOMContentLoaded, when to use load, and when simply moving a <script> tag is enough.
Concept
JavaScript can run before the browser has finished building the page.
When the browser reads HTML from top to bottom, it parses elements as it encounters them. If it finds a normal <script> tag, it usually pauses HTML parsing, downloads the script if needed, and executes it immediately. That means code in a script inside <head> may run before <body> elements exist in the DOM.
This matters because many scripts need to do things like:
- find elements with
document.getElementById(...) - attach event listeners to buttons or forms
- change text or styles in the page
- initialize UI components
If those elements have not been parsed yet, your code may fail or return null.
There are several ways to solve this:
- listen for the
DOMContentLoadedevent - listen for the
loadevent - place the script at the end of
<body> - use the
deferattribute on the script
DOMContentLoaded
This event fires when the HTML document has been fully parsed and the DOM is ready. It does not wait for images, stylesheets in every case of rendering completion, or other external resources to fully finish loading.
Mental Model
Think of the browser like a person assembling furniture from instructions.
- The HTML is the instruction manual being read from top to bottom.
- The DOM is the assembled furniture.
- A script in
<head>is like interrupting the person halfway through and asking them to use a drawer that has not been assembled yet.
DOMContentLoaded means: the furniture is assembled enough to use.
load means: the furniture is assembled and every extra item in the room has also arrived.
If you only need the page structure, wait for DOMContentLoaded. If you need everything, including images and external resources, wait for load.
Syntax and Examples
Basic syntax with DOMContentLoaded
document.addEventListener('DOMContentLoaded', function () {
const message = document.getElementById('message');
message.textContent = 'The DOM is ready!';
});
This waits until the browser has parsed the HTML and built the DOM. After that, #message can be accessed safely.
Using load
window.addEventListener('load', function () {
console.log('Entire page and resources are loaded');
});
Use this when your code depends on things like image dimensions or fully loaded external assets.
Modern shorthand with arrow function
document.addEventListener('DOMContentLoaded', {
button = .();
(button) {
button.(, {
.();
});
}
});
Step by Step Execution
Consider this HTML:
<!DOCTYPE html>
<html>
<head>
<script>
document.addEventListener('DOMContentLoaded', function () {
const box = document.getElementById('box');
box.textContent = 'Ready';
});
</script>
</head>
<body>
<div id="box">Loading...</div>
</body>
</html>
Here is what happens step by step:
- The browser starts parsing the HTML.
- It reaches the
<script>inside<head>. - The script runs immediately.
- That script does not try to access
#boxyet.
Real World Use Cases
This concept appears in many real applications:
Initializing UI components
A script may need to find menus, modals, tabs, or forms before attaching behavior.
document.addEventListener('DOMContentLoaded', () => {
setupMenu();
setupTabs();
});
Attaching form validation
Form elements must exist before event listeners can be attached.
document.addEventListener('DOMContentLoaded', () => {
const form = document.querySelector('#signup-form');
if (form) {
form.addEventListener('submit', validateForm);
}
});
Reading image size after full page load
If you need actual image dimensions, load can be more appropriate.
window.addEventListener('load', () => {
const img = .();
.(img., img.);
});
Real Codebase Usage
In real codebases, developers usually do more than just attach one listener.
Common patterns
1. Initialization function
function init() {
const nav = document.querySelector('.nav');
const form = document.querySelector('#contact-form');
if (nav) {
setupNav(nav);
}
if (form) {
setupForm(form);
}
}
document.addEventListener('DOMContentLoaded', init);
This keeps startup logic organized.
2. Guard clauses
Developers often check whether an element exists before using it.
document.addEventListener('DOMContentLoaded', () => {
const modal = document.querySelector('.modal');
if (!modal) return;
modal.classList.();
});
Common Mistakes
1. Accessing elements too early
Broken code:
<head>
<script>
const el = document.getElementById('app');
el.textContent = 'Hello';
</script>
</head>
Problem:
#appmay not exist yet.
Fix:
document.addEventListener('DOMContentLoaded', () => {
const el = document.getElementById('app');
if (el) {
el.textContent = 'Hello';
}
});
2. Using window.onload when DOMContentLoaded is enough
window. = () {
.();
};
Comparisons
| Approach | When it runs | Best for | Notes |
|---|---|---|---|
document.addEventListener('DOMContentLoaded', ...) | After HTML is parsed | Accessing DOM elements | Most common for DOM setup |
window.addEventListener('load', ...) | After full page and resources load | Image sizes, asset-dependent logic | Runs later |
Script at end of <body> | After most HTML is parsed | Simple pages | Easy but less explicit |
<script defer> | After HTML parsing completes | External scripts in <head> | Great modern default |
Cheat Sheet
Quick reference
Run code when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
// safe to access DOM elements
});
Run code when entire page is loaded
window.addEventListener('load', () => {
// images and other resources are loaded too
});
Load external script in <head> safely
<script src="app.js" defer></script>
Rules of thumb
- Need HTML elements? Use
DOMContentLoaded. - Need images or full assets? Use
load. - Using an external script in
<head>? Preferdefer. - Want simple HTML? Put the script before
</body>.
FAQ
What is the difference between DOMContentLoaded and load?
DOMContentLoaded fires when the HTML has been parsed and the DOM is ready. load fires later, when the whole page and its resources have finished loading.
Should I use DOMContentLoaded or window.onload?
Use DOMContentLoaded for most DOM-related tasks. Use window.onload only when you need images or other external resources to be fully loaded.
Is putting the script at the bottom of <body> still valid?
Yes. It is still a simple and valid way to make sure most of the DOM exists before the script runs.
Is defer better than DOMContentLoaded?
Not exactly better, but often convenient for external scripts in <head>. defer delays execution until after parsing. You can still use DOMContentLoaded inside deferred scripts if needed.
Why does my script work sometimes and fail other times?
This usually happens because of timing. If your script runs before the DOM elements exist, it may fail depending on browser speed, network timing, or page structure.
Mini Project
Description
Build a small page that updates a status message and attaches a click handler only after the DOM is ready. This demonstrates how to safely work with elements that appear in the <body> while the script is loaded from the <head>.
Goal
Create a page that waits for the document to be ready, updates text content, and responds to a button click without timing errors.
Requirements
- Load the JavaScript file from the
<head>. - Wait until the DOM is ready before accessing
<body>elements. - Update a status message on the page.
- Attach a click listener to a button.
- Show a result message when the button is clicked.
Keep learning
Related questions
Allow Only Numeric Input (0-9) in HTML Input Using jQuery
Learn how to allow only digits 0-9 in an HTML input using jQuery, with examples, validation tips, common mistakes, and best practices.
CSS :not() Selector for Excluding a Class or Attribute
Learn how to use the CSS :not() selector to target elements that do not have a specific class or attribute, with examples and common mistakes.
Can HTML Checkboxes Be Readonly? Understanding readonly vs disabled in HTML Forms
Learn why HTML checkboxes do not support readonly, how disabled differs, and practical ways to prevent changes while still submitting values.