Browsers parse HTML top to bottom. A ordinary <script src="..."> without async, defer, or type="module" fetches the file, then runs it immediately, which pauses parsing until execution finishes. CSS can block rendering in similar ways. The sections below map those defaults to async, defer, and ES modules.
When is HTML rendering blocked?
On initial page load, a parser-blocking script stops further HTML processing until the file is downloaded and executed. Stylesheets can block rendering while they load.
<script src="file.js"></script>
Blocking is costly for two reasons: the user waits longer for visible content, and inline scripts that run early may call document.getElementById before later markup exists.
Async JS loading
Placing scripts at the end of <body> avoids some ordering problems. The async attribute goes further: the file downloads in parallel with parsing, but execution still interrupts parsing when the download completes.
<script src="file.js" async></script>
Scripts with async run in unpredictable order relative to one another. Use async for independent snippets (analytics, ads) where order does not matter.
Deferred JS loading
defer also downloads in parallel with parsing, but the browser runs deferred classic scripts only after the document is parsed, in document order.
<script src="file.js" defer></script>
Multiple deferred scripts run in the order they appear in the HTML. That makes defer the usual choice for application bundles that depend on each other.
Module scripts
ES module scripts default to defer-like behavior: they fetch in parallel, run after parsing, and preserve order among module scripts. They also run in strict mode and use import/export.
<script type="module" src="app.js"></script><script nomodule src="legacy.js"></script>import() inside a module still loads asynchronously at the call site. For how modules resolve, see A quick introduction to modules.
HTTP/2 and many small files
With HTTP/2, many parallel downloads are cheaper than they were on HTTP/1.1, but each classic script still pays a parse-and-run cost. Bundling and defer or type="module" remain relevant; multiplexing does not remove execution order concerns.

Use async when execution order does not matter. Use defer or type="module" when scripts must run after the DOM is parsed and, for classic scripts, in source order.