JAVASCRIPT / ERRORS, STRICT MODE, AND DEBUGGING
Strict mode and the mistakes it forbids
Turn on strict mode in the right place and predict exactly which sloppy behaviours it converts into ReferenceError, TypeError, or a parse-time SyntaxError.
What you will learn
- Place "use strict" as the very first statement of a script or function body.
- Predict when an assignment throws ReferenceError instead of creating a global.
- Explain why this is undefined in a detached method call under strict mode.
- Skip the directive in ES modules and class bodies, which are strict already.
Understanding Strict mode and the mistakes it forbids
"use strict"; is a directive prologue: a bare string expression that must sit before any other code in a script or in a function body. Comments may precede it, but a single statement before it demotes it to an ordinary string expression that is evaluated and thrown away, leaving the code sloppy. The function-level form exists so that one function inside an old file can be migrated without touching the rest. ES modules and class bodies are strict code by definition, so most code written today is strict without anyone typing the string.
Strict mode adds no features; it removes the language's silent failures. The changes fall into three groups: operations that used to succeed wrongly and now throw where you made the mistake (assigning to an undeclared name raises ReferenceError instead of creating a global, writing to a frozen or getter-only property raises TypeError); constructs refused before the code runs at all (duplicate parameter names, with, delete on a plain variable, leading-zero octal literals); and quiet semantic changes (this stays undefined in a plain call instead of becoming the global object, and arguments stops aliasing the named parameters).
Why those particular things? Ask what an engine can know about an identifier before running the code. Implicit globals, with, and sloppy eval all mean a binding can appear or change during execution, so no identifier can be resolved to a fixed storage slot at parse time. Strict mode removes each of them, which makes scopes statically resolvable — that is why strict code compiles better and why modules were never given the sloppy option.
function sloppy() {
total = 5; // never declared
const limits = Object.freeze({ max: 1 });
limits.max = 99; // write refused, no complaint
return "total leaked to globalThis: " + (globalThis.total === 5) +
", limits.max: " + limits.max;
}
function strict() {
"use strict";
let report = "";
try {
subtotal = 5;
} catch (err) {
report += err.name;
}
const limits = Object.freeze({ max: 1 });
try {
limits.max = 99;
} catch (err) {
report += " then " + err.name;
}
return report + ", subtotal on globalThis: " + ("subtotal" in globalThis);
}
console.log(sloppy());
console.log(strict());Strict mode replaces JavaScript's silent failures with errors raised at the exact spot where the mistake was written.
Worked examples
this in a plain call
Shows that strict mode stops substituting the global object for a missing receiver.
function receiver() {
return this;
}
function strictReceiver() {
"use strict";
return this;
}
console.log(receiver() === globalThis);
console.log(strictReceiver());
const counter = {
n: 0,
bump() {
"use strict";
this.n += 1;
return this.n;
}
};
const detached = counter.bump;
try {
detached();
} catch (err) {
console.log(err.name);
}Example explained
Line 1receiver() is called with no receiver, so sloppy mode substitutes globalThis for this and the comparison is true.
Line 2strictReceiver() performs no substitution, so this keeps whatever the call site supplied: undefined.
Line 3const detached = counter.bump copies the function without the object, so this.n += 1 reads a property of undefined and throws TypeError.
Line 4Without the directive that same call would have quietly done globalThis.n += 1 and left NaN behind.
Constructs that never get to run
Demonstrates the bans that are early errors, and one that only fails at run time.
"use strict";
function check(source) {
try {
eval(source);
return "ran fine";
} catch (err) {
return err.name;
}
}
console.log(check("function dup(a, a) { return a; }"));
console.log(check("with (Math) { }"));
console.log(check("var v = 1; delete v;"));
console.log(check("delete Math.PI;"));Example explained
Line 1A direct eval inside strict code compiles its argument as strict code, which is how these bans can be shown without making the file itself unparseable.
Line 2function dup(a, a) is legal sloppy JavaScript where the second a wins; strict mode rejects it because a reference to a would be ambiguous.
Line 3with and delete v are refused for the same underlying reason: both leave the engine unable to decide at parse time which binding an identifier names.
Line 4delete Math.PI parses fine and fails when it runs, because the property is non-configurable and strict mode throws instead of returning false.
Class bodies are strict already
Shows strict behaviour inside a class in a file with no directive anywhere.
class Counter {
constructor() {
this.n = 0;
}
bump() {
this.n += 1;
return this.n;
}
leak() {
tally = 1;
return tally;
}
}
const c = new Counter();
console.log(c.bump());
try {
c.leak();
} catch (err) {
console.log(err.name);
}
const detached = c.bump;
try {
detached();
} catch (err) {
console.log(err.name);
}Example explained
Line 1No directive appears anywhere in the file, yet everything between class Counter { and its closing brace is strict code by definition.
Line 2tally = 1 inside leak has no binding to write to, so it throws ReferenceError rather than creating globalThis.tally.
Line 3detached() invokes bump with no receiver, this stays undefined, and this.n throws TypeError.
Line 4Run as a classic script the surrounding top level is still sloppy: strictness attaches to each code unit, not to the whole file.
Important notes
The sloppy halves of these examples only behave as shown in a classic script, a CommonJS file, or the browser console. Inside an ES module everything is strict and the contrast vanishes.
A leading-zero literal such as 0644 is a SyntaxError in strict code instead of octal 420, so write 0o644. Strictness is decided at parse time and cannot be switched back off from inside strict code.
Common mistakes
Writing the directive after the first require, const, or any other statement. The string is then just an expression that is evaluated and discarded, the code stays sloppy, and it looks as if strict mode changed nothing.
Putting "use strict" inside a function with default, rest, or destructured parameters. function f(a = 1) { "use strict"; } is itself a SyntaxError, so the whole file fails to parse.
Expecting strict mode to catch typos. user.naem is undefined in both modes, and reading an undeclared variable throws ReferenceError in both modes; only assignment to an undeclared name changes behaviour.
Try it yourself
Change, predict, then run
In the browser console, define function add() { n = 1; return n; }, call it, and check that "n" in globalThis is true. Then redefine the same function with "use strict"; as its first line, call it again, and compare the two results.
Open the JavaScript workspaceCheck your understanding
A classic script contains no "use strict" anywhere. One of its classes has a method whose body runs total = 1, and total is declared nowhere. What happens the first time that method is called?
- A ReferenceError is thrown, because a class body is strict code no matter what surrounds it.
- globalThis.total is created and set to 1, because the script itself is sloppy.
- A SyntaxError is reported when the file is parsed, before any of it runs.
- The assignment is silently discarded, the same way a write to a frozen property is.
Show answer
Class bodies, like ES modules, are strict code by definition, so the assignment has no binding to target and throws ReferenceError. Option 1 is tempting because strictness normally arrives through a directive, but strictness is a property of each code unit rather than of the file. Option 2 is wrong because whether an identifier has a binding is only decidable while the code runs, not at parse time.