AlgoInsight

Free lesson · JavaScript Basic to Advance

Strict Mode and Top-Level await

Easy5 min read

Two Features That Quietly Shape Modern JavaScript

Strict mode and top-level await aren't features you actively use — they're features that change the rules around your code. Both have appeared throughout earlier modules without much explanation. This Topic explains them properly so you understand the environment your code runs in.

"use strict";   // explicit opt-in to strict mode

// In strict mode, this throws
try {
  undeclaredVar = 5;
} catch (e) {
  console.log("Error:", e.message);
}

// In non-strict mode, the same code would silently create a global variable

Strict mode tightens JavaScript's behavior in specific ways. Modules and class bodies are strict by default — most modern code runs in strict mode without explicitly opting in.

Top-level await lets you use await at the top level of a module, outside any async function. It's a small feature but eliminates one of the most awkward patterns in module initialization.

This Topic covers both. Neither is large, but knowing the rules they impose makes other JavaScript behaviors make sense.

Strict Mode

Strict mode is a way to opt into a stricter version of JavaScript's runtime rules. It was added in ES5 to fix some of the language's worst defaults. The "use strict" directive turns it on for a file or function:

"use strict";

// Several behaviors become errors instead of silent failures
try {
  undeclaredVariable = "creates a global in non-strict, throws in strict";
} catch (e) {
  console.log("1:", e.message);
}

// Duplicate parameter names became an error (was allowed in non-strict)
try {
  eval('function f(a, a) { return a; }');
} catch (e) {
  console.log("2:", e.message);
}

// 'this' in plain function calls is undefined instead of global
function whatIsThis() {
  return this;
}
console.log("3:", whatIsThis());   // undefined in strict, globalThis in non-strict

The main things strict mode changes:

Assigning to undeclared variables throws. Without strict mode, someVar = 5 creates a global variable if someVar wasn't declared. This is the source of countless bugs — typos in variable names silently leak globals. Strict mode makes this an error.

this in plain function calls is undefined. Without strict mode, it would be the global object. This matters for the this rules from Topic 8.1 — the four rules behave consistently in strict mode.

Duplicate parameter names are errors. function f(a, a) {} was previously allowed and would use the last value. Strict mode rejects it.

Octal literals are forbidden. 077 was octal in non-strict (= 63 decimal). In strict mode, you must use 0o77 explicitly to opt into octal.

delete on variables throws. delete someVar was a no-op in non-strict; in strict mode it's an error.

with statements are forbidden. A rarely-used statement that was problematic; strict mode bans it entirely.

You probably won't encounter most of these in modern code. The most-impactful change is the first one — accidental globals are far more common than the others, and strict mode makes them visible.

Interactive animation: The typo that used to become a global - in strict mode the assignment throws instead of leaking silently.

How You Get Strict Mode

In modern JavaScript, you don't usually have to write "use strict" yourself. Strict mode is automatic in several contexts:

ES Modules (files using import/export, covered in Module 11) are always strict. Every modern bundled and built JavaScript application is strict by default.

Class bodies are always strict. We saw this in Topic 8.2.

async functions and generator functions are strict.

The only place you'd write "use strict" explicitly is at the top of a script file (not a module) that you want to run in strict mode. This is becoming rare — most modern projects use modules everywhere.

In practice, you can assume your modern JavaScript code runs in strict mode unless you're working with very old script-tag-based code.

Why It Matters

Even though you don't actively opt in, strict mode shapes the behavior of code you write:

class Counter {
  count = 0;

  increment() {
    this.count++;
    return this.count;
  }
}

const c = new Counter();
const inc = c.increment;

try {
  console.log(inc());   // throws — this is undefined in strict mode
} catch (e) {
  console.log("Caught:", e.message);
}

This behavior — methods losing their this and throwing when detached — only exists because class bodies are strict. In non-strict mode, the same code would silently work (incorrectly) by treating the global object as this. The error you see is strict mode making the bug visible.

Knowing this rule explains a lot of JavaScript behaviors. "Why does my function throw when I detach it from an object?" Strict mode. "Why does my typo create a silent global?" You're not in strict mode. The rules behave differently in the two modes, and knowing which mode you're in matters.

Interactive animation: Bare-call this, two worlds - sloppy mode falls back to the global object, strict mode leaves this undefined.

Top-Level await

Until ES2022, await was only allowed inside async functions. If you wanted to do async work at the top of a module — load configuration, wait for an API, initialize a database connection — you had to wrap it in an immediately-invoked async function:

// Old pattern — IIFE for module-level async
(async () => {
  const config = await fetch("/config").then(r => r.json());
  // ... rest of module setup
})();

// New pattern — top-level await (ES2022+)
const config = await fetch("/config").then(r => r.json());
// ... rest of module setup

The new pattern is cleaner. The module itself becomes async; importing modules wait for it to finish loading before their code runs.

Top-level await only works inside ES modules. It's not supported in regular scripts (the old <script src="..."> style) or in CommonJS modules (the require-based Node module system). For modern bundled code that uses modules everywhere, it's fully supported.

// In a module file (modern Node, modern bundlers)
const data = await fetch("https://api.example.com/data").then(r => r.json());
const userId = await getCurrentUserId();
const userPrefs = await loadPreferences(userId);

export const initialState = {
  data,
  userId,
  userPrefs
};

This is clean. The module exports values that required async setup, and consuming modules wait until those values are ready. The IIFE wrapper goes away.

The trade-off: importing modules wait for the top-level async work to complete. Long-running async initialization at the top of a module can slow down the entire load. Use top-level await for genuinely needed initialization, not for "nice to have" data fetches.

Common Pitfalls

Warning: The strict mode and top-level await traps.

Assuming you're not in strict mode when you actually are. Modules and classes are strict by default. If your code throws on something that "should" silently work, strict mode is usually the reason.

Top-level await in scripts, not modules. It only works in modules. Old-style script tags don't support it.

Long-running top-level await. Anything you await at the top level blocks consuming modules. Use this for genuinely needed initialization, not for slow optional data.

Forgetting that async functions are strict. this in a plain call to an async function is undefined, not the global object. This catches people who don't realize they're in strict mode.

Why This Matters in Interviews

Interview: Strict mode questions are uncommon but appear at senior levels. "What does strict mode do?" or "What's the difference between strict mode and non-strict?" expect the major rule changes.

Top-level await is a newer feature. Some interviewers ask "How would you load configuration at module load time?" expecting top-level await as the modern answer, with the IIFE pattern as the older alternative.

Topic 10.7 — Interview Lens: Modern JavaScript Patterns — covers these alongside other modern features.

Try It

Exercise: Pick three behaviors that strict mode changes (assigning to undeclared variables, this in plain function calls, duplicate parameter names, etc.). Write a small example for each that demonstrates the difference.

Then think about which of these have caught you in real code. Most JavaScript developers have hit "accidentally created a global by typo" at least once — strict mode is your defense against that pattern.

Back to JavaScript Basic to Advance