JAVASCRIPT / GETTING STARTED
Running your first script with script tags
Write a working HTML page that runs JavaScript from an inline <script> and an external file, and predict the order each script executes in.
What you will learn
- Run JavaScript from an inline <script> and from an external file with src
- Predict which script runs first from the position of each tag in the HTML
- Explain why a head script logs null when it looks for a body element
- Use defer to keep a script tag in <head> but run it after the DOM is parsed
Understanding Running your first script with script tags
The <script> element is the seam between HTML and JavaScript: either you put the code between the opening and closing tags, or you point src at a .js file and leave the tags empty. The browser reads an HTML document from top to bottom, and when the parser reaches a script tag it stops building the page, hands that code to the JavaScript engine, waits for it to finish, then resumes parsing. That is the whole mental model — a script tag is not a declaration that merely sits somewhere, it is a moment in time during page construction.
Because of that timing, a script in <head> runs before <body> exists, so document.querySelector('h1') has nothing to find and returns null. The old fix is to put the tag just before </body>, where everything above it has already been parsed. The modern fix is the defer attribute, which lets the file download in parallel and runs it only after the document is fully parsed. Both buy the same guarantee: the elements the script touches already exist when it runs.
Several script tags on one page are not separate programs. They share a single global environment and, unless you use defer or async, they run in document order, so whatever the first tag loaded has already finished by the time the second starts. One tag does one job, though: if src is present, anything written between the tags is ignored, which is why mixing the two forms silently loses code.
<!DOCTYPE html>
<html>
<head>
<script>
console.log('head:', document.querySelector('h1'));
</script>
</head>
<body>
<h1>Hello</h1>
<script>
console.log('body:', document.querySelector('h1').textContent);
console.log('scripts run in parse order');
</script>
</body>
</html>A script tag executes at the exact moment the HTML parser reaches it, so its position decides both what already exists on the page and what runs next.
Worked examples
Loading a separate file with src
Shows that an external script runs in document order and that code written inside a src tag is thrown away.
<!-- index.html -->
<!DOCTYPE html>
<html>
<body>
<script src="second.js">
console.log('never printed');
</script>
<script>
console.log('inline after');
</script>
</body>
</html>
// second.js
console.log('second.js ran');Example explained
Line 1The parser stops at the src tag, fetches second.js, and runs it before moving on.
Line 2Code between the tags of a src script is ignored, so 'never printed' never reaches the console.
Line 3The inline tag below runs next, which is why the two lines appear in document order.
Line 4The closing </script> is required even when the tag is empty.
Keeping a script in the head with defer
Shows how defer moves a head script's execution to after the document is parsed.
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<script defer src="late.js"></script>
<script>
console.log('plain head script');
</script>
</head>
<body>
<p id="msg">before</p>
<script>
console.log('body script');
</script>
</body>
</html>
// late.js
console.log('deferred, #msg is', document.getElementById('msg').textContent);Example explained
Line 1defer downloads late.js without pausing the parser and postpones running it until parsing is done.
Line 2The second head script has no defer, so it blocks parsing and logs first even though its tag comes later.
Line 3By the time late.js runs, the paragraph exists, so getElementById returns the element instead of null.
Line 4defer only has an effect on tags that have src; on an inline script it does nothing.
Important notes
type="text/javascript" is a leftover from older HTML and can be dropped; a bare <script> is already JavaScript. type="module" is not the same thing — it defers automatically, gets its own scope, and is blocked on file:// URLs.
A wrong src path produces no JavaScript error at all: you get a 404 in the Network tab and total silence, so check the path relative to the HTML file rather than to your project root.
Common mistakes
Writing <script src="app.js" /> — HTML has no self-closing script tag, so the browser treats the rest of the file as script text and the page below it never renders.
Putting code between the tags of a script that already has src: the file loads, the inline code is discarded, and edits made there appear to do nothing.
Leaving a DOM-touching script in <head> without defer, which throws 'Cannot read properties of null' because the element it looks for has not been parsed yet.
Try it yourself
Change, predict, then run
Build an index.html with <h1 id="t">Loading</h1> and two script tags, one placed before the heading and one after it, each logging document.getElementById('t'). Reload the page and write down why the first logs null while the second logs the element.
Open the JavaScript workspaceCheck your understanding
A page has <script src="a.js"></script> in the head, then <script defer src="b.js"></script> in the head, then an inline <script> just before </body>. In what order does their code run?
- a.js, then the inline body script, then b.js
- a.js, then b.js, then the inline body script
- b.js, then a.js, then the inline body script
- The inline body script first, because inline code never waits for downloads
Show answer
a.js blocks the parser where it sits, so it runs first. The inline script runs when the parser reaches it near the end of the body. b.js is deferred, which postpones it until the whole document is parsed, so it runs last even though its tag appears second. Option 2 is tempting because scripts normally run in document order, but defer removes b.js from that parse-time queue.