JavaScript Strict Mode Explained
Strict mode turns silent JavaScript mistakes into thrown errors and closes off unsafe legacy behavior. Modules and classes use it by default.
Strict mode is an opt-in variant of JavaScript that changes several of the language’s default behaviors — turning silent mistakes into thrown errors, disabling a handful of confusing legacy features, and tightening how variables and this behave. You enable it by putting the exact string "use strict"; at the top of a script or function, though in modern JavaScript you often get it for free: ES modules and class bodies are strict by default, with no directive required.
What changes under strict mode
Assigning to an undeclared variable throws instead of creating a global. In non-strict code, x = 5 without let, const, or var silently creates a global variable — a classic source of bugs where a typo’d variable name in a nested function quietly pollutes global scope instead of failing loudly. Strict mode throws a ReferenceError instead.
Assignments that silently fail now throw. Writing to a read-only property, a getter-only property, or a non-extensible object does nothing in non-strict mode — the assignment is simply ignored. Under strict mode, each of these throws a TypeError, surfacing a bug that would otherwise fail silently and only show up much later when the value turns out not to have changed.
this inside a plain function call is undefined, not the global object. Call a regular function without a receiver — myFunction() rather than obj.myFunction() — and in non-strict mode this inside it silently becomes the global object (window in browsers). That’s rarely what anyone intends, and it’s an easy way to accidentally mutate global state. Strict mode leaves this as undefined in that case, so a piece of code that wrongly assumes it has a receiver throws immediately instead of quietly writing to the wrong object. This is a big part of why understanding how this is determined matters more in some codebases than others — strict mode removes one of the most common accidental bindings.
Duplicate parameter names are a syntax error. function f(a, a) { } is legal, if confusing, in non-strict mode — the second a silently shadows the first. Strict mode rejects it outright at parse time.
Octal literals without the 0o prefix are disallowed. Legacy octal syntax like 010 (meant as octal 8, easily misread as decimal 10) is a syntax error in strict mode; the unambiguous 0o10 is the way to write it.
eval and arguments are more predictable. In non-strict mode, eval() can introduce new variables into the enclosing scope, and mutating the arguments object can retroactively change the value of a named parameter it aliases. Strict mode removes both of these — eval gets its own scope, and arguments is a static snapshot disconnected from the named parameters.
delete on a plain variable, function, or non-configurable property is a syntax or runtime error. Non-strict mode silently no-ops the attempt (or, for a bare identifier, was already unusual enough that browsers varied); strict mode makes the invalid usage an explicit error instead.
Why this exists: fixing mistakes without breaking the web
JavaScript can’t remove its early design mistakes outright — doing so would break existing websites built on those behaviors, which is a constraint the language has taken seriously since its earliest standardization. Strict mode is the workaround: rather than changing the default behavior of all JavaScript, ECMAScript 5 added an opt-in mode that tightens the rules, and every script, function, or module that doesn’t explicitly (or implicitly) request it keeps running under the old, looser rules indefinitely.
That’s also why the directive is a plain string rather than a keyword — "use strict" is written so that JavaScript engines that predate ES5 simply parse it as a harmless, unused string expression and ignore it, instead of failing to parse the file at all.
Where strict mode applies automatically
You rarely need to write the directive by hand in modern JavaScript, because two increasingly dominant contexts are strict by default:
- ES modules (any file loaded via
import/export, or a<script type="module">) are always strict, no directive needed. - Class bodies are always strict, including code inside constructors and methods, regardless of whether the surrounding file uses
"use strict".
Given how much modern JavaScript is written using ESM and classes, a large fraction of code shipped today runs under strict mode without anyone adding the directive explicitly. It’s mostly older CommonJS-style scripts and plain top-level <script> tags without type="module" where strict mode remains opt-in.
Function-level vs. script-level strict mode
The directive can go at the top of a whole script (making everything in that file strict) or at the top of an individual function body (making only that function, and anything nested inside it, strict — while sibling code outside it stays non-strict):
function strictFn() {
"use strict";
x = 10; // ReferenceError: x is not defined
}
function looseFn() {
y = 10; // Silently creates a global
}
Mixing the two within one file is legal but rarely a good idea — it’s easy to lose track of which functions are strict and which aren’t. In practice, most codebases either enable it file-wide via the top-level directive, or get it for free by using modules and classes throughout.
The takeaway
Strict mode converts a handful of JavaScript’s historically silent failure modes — accidental globals, no-op assignments, ambiguous this binding, confusing eval and arguments aliasing — into errors that surface immediately instead of causing hard-to-trace bugs later. You rarely need to add "use strict" by hand anymore, since ES modules and class bodies are strict automatically, but understanding what it changes explains a real category of behavioral differences between older non-strict scripts and the modules-and-classes code most projects write today.
Tagged
Keep reading
Takina · · 4 min read Web Locks API: Coordinating Work Across Browser Tabs
The Web Locks API lets JavaScript acquire named locks shared across tabs, so only one tab does work like a token refresh or a write at a time.
Takina · · 4 min read Svelte 5 Runes Explained
Svelte 5 runes like $state and $derived replace the old reactive-assignment magic with explicit function calls that work anywhere in a file.
Takina · · 4 min read Shallow Copy vs Deep Copy in JavaScript
A shallow copy duplicates an object's top-level properties but shares nested references; a deep copy duplicates everything recursively. How to do each.