JavaScript Where To
External vs internal <script>, where to place them, and why <script defer> wins.
JavaScript runs in HTML pages via the <script> tag. There are exactly three ways to attach JS to a page:
- External file —
<script src="app.js"></script>(production-grade). - Internal block —
<script>...</script>(small demos, inline tutorials). - Inline event handler —
<button onclick="...">(legacy; avoid in new code).
Production rule: ship JS as external files with the
deferattribute. Browsers cache external files;deferkeeps the page interactive while the script downloads.
| Where | When it runs | Pros | Cons |
|---|---|---|---|
<head> (no attributes) | Blocks HTML parsing until script downloads + executes | Simple | Slow page load |
<head> + defer | Downloads in parallel, executes after HTML parses | Best for app code | DOM-ready guaranteed |
<head> + async | Downloads in parallel, executes ASAP (out-of-order) | Best for analytics | Can run before DOM exists |
Bottom of <body> | Executes after the HTML above it | DOM is already parsed | Slower to start downloading |
Step 1. Create app.js:
javascriptdocument.getElementById("demo").innerHTML = "Loaded from app.js!"; console.log("app.js executed");
Step 2. Create index.html and link the external file with defer:
html<!DOCTYPE html> <html> <head> <title>Where To Demo</title> <script src="app.js" defer></script> </head> <body> <p id="demo">Loading…</p> </body> </html>
Step 3. Open index.html in a browser.
Loaded from app.js!
And in DevTools console:
app.js executed
- The browser hit
<script src="app.js" defer>in<head>. - Because of
defer, it began downloadingapp.jsin parallel with HTML parsing. - It finished parsing the rest of the page (including
<p id="demo">). - Once the DOM was ready and
app.jshad downloaded, it executed — andgetElementByIdfound the<p>because the DOM was complete.
Without defer, the browser would have paused parsing at the <script> tag, blocking everything below it.
| Attribute | Order preserved? | Waits for DOM? | Use for |
|---|---|---|---|
defer | ✅ Yes | ✅ Yes (executes before DOMContentLoaded) | App code, libraries |
async | ❌ No (whichever finishes first) | ❌ No | Independent scripts (analytics, ads) |
| neither | ✅ Yes | ❌ No (blocks parsing) | Avoid in modern code |
html<!-- App code: defer keeps execution order --> <script src="vendor.js" defer></script> <script src="app.js" defer></script> <!-- Analytics: async ships fastest --> <script src="https://www.googletagmanager.com/gtag/js" async></script>
With defer, scripts execute in the order they appear in HTML. With async, order is whichever downloads first. If your app.js depends on vendor.js, you must use defer (or ESM import).
Modern apps use <script type="module">:
html<script type="module" src="main.js"></script>
Modules are:
- Always deferred (no need for the
deferattribute). - CORS-restricted when loaded from
file://— open via a local dev server. - Allow
import/exportsyntax across files.
Don't want to wire up an HTML file? Use Codekilla's online JavaScript compiler — paste your code, click Run, see output.
<script>in<head>withoutdefer— blocks page rendering until the script downloads. Slow first paint.- Targeting
getElementByIdfrom a head-script without defer — DOM doesn't exist yet, returnsnull. - Mixing
asyncanddeferon dependent scripts —asyncruns out of order, breaking dependencies. - Inline
onclickin production — clutters HTML, breaks Content-Security-Policy. UseaddEventListenerinstead. - Forgetting the
.jsextension in<script src="...">— most servers serve it correctly anyway, but tooling complains.
- External move — Take a page with an inline
<script>...</script>block and move the code intoapp.js. Adddeferand confirm it still works. Hint: link with<script src="app.js" defer></script>. - Order test — Create three scripts that each
console.logtheir name. Reload withdeferthenasync— observe console order. Hint:deferkeeps insertion order;asyncis a race. - DOM-ready demo — Place a
<script>in<head>(no defer) that tries to read<p id="demo">. Observe thenull. Adddeferand watch it work. Hint: console.log the result ofgetElementById("demo").
💡 Think Like a Programmer: Default to
<script src="app.js" defer></script>in<head>. It's the modern, fastest, most predictable placement. Saveasyncfor independent third-party tags (analytics, ads).
Quick recap quiz?
We'll generate 5 MCQs from this lesson and check your understanding instantly. Takes ~30 seconds.
