Strict Mode & Common Pitfalls
JavaScript was designed in a hurry in 1995, and some of its early decisions turned silent mistakes into "features". Strict mode removes many of them. This lesson covers how strict mode works and the classic pitfalls — with and without it — that catch developers at every level.
Enabling strict mode
Put "use strict"; at the very top of a script or function. You often don't need to: ES modules and classes are always strict, so most modern code already is.
"use strict"; let userName = "Ana"; usrName = "Ben";
Uncaught ReferenceError: usrName is not defined
Without strict mode, that typo would silently create a new global variable called usrName, and userName would stay unchanged — a bug that can take hours to find.
What strict mode changes
| Situation | Sloppy mode | Strict mode |
|---|---|---|
| Assigning to an undeclared variable | Creates a global | ReferenceError |
| Writing to a read-only or frozen property | Silently ignored | TypeError |
this in a plain function call | The global object | undefined |
| Duplicate parameter names | Allowed | SyntaxError |
with statements, octal literals like 010 | Allowed | SyntaxError |
Every change turns a silent failure into a visible error, which is exactly what you want.
Pitfall: == instead of ===
console.log("" == 0, "0" == false, null == 0, [] == false); console.log("" === 0, "0" === false, null === 0, [] === false);
true true false true false false false false
Loose equality's conversion rules are unpredictable. Use === everywhere.
Pitfall: floating-point maths
console.log(0.1 + 0.2 === 0.3); console.log(1.005 * 100);
false 100.49999999999999
Round for display and store money as integer cents (lesson 16).
Pitfall: string concatenation with numbers
const fromInput = "5"; console.log(fromInput + 1); console.log(Number(fromInput) + 1);
51 6
Pitfall: mutating shared objects
const defaults = { tags: [] }; const a = { ...defaults }; const b = { ...defaults }; a.tags.push("urgent"); console.log(b.tags);
[ 'urgent' ]
Spread copies are shallow: a.tags and b.tags are the same array. Use structuredClone, or create fresh nested values each time.
Pitfall: sorting numbers
console.log([10, 9, 1, 100].sort()); console.log([10, 9, 1, 100].sort((a, b) => a - b));
[ 1, 10, 100, 9 ] [ 1, 9, 10, 100 ]
Pitfall: automatic semicolon insertion and return
function getConfig() { return { debug: true; } } console.log(getConfig());
undefined
A line break right after return ends the statement, so the function returns undefined and the object below is never reached. Always start the returned value on the same line as return.
Pitfall: losing this
"use strict"; const counter = { count: 0, increment() { this.count++; }, }; const fn = counter.increment; try { fn(); } catch (err) { console.log(err.constructor.name, "— this was undefined"); } setTimeout(() => counter.increment(), 0); setTimeout(() => console.log("count:", counter.count), 5);
TypeError — this was undefined count: 1
Passing a method as a callback detaches it from its object. Wrap it in an arrow function or use bind (lesson 27).
Pitfall: forgetting await
async function getTotal() { return 42; } const total = getTotal(); console.log(total + 1);
[object Promise]1
Without await, you get the promise, not its value. Linters can catch this ("no-floating-promises"), and TypeScript catches it automatically.
Pitfall: typeof null and arrays
console.log(typeof null, typeof [], Array.isArray([]));
object object true
Tools that catch these for you
- ESLint — flags
==, unused variables, unreachable code, missingawaits and hundreds of other problems as you type. - Prettier — formats code consistently, which makes ASI surprises and mismatched braces obvious.
- TypeScript — adds types, catching wrong-type bugs before the code runs (see the TypeScript course).