Glossary / Strict mode

JavaScript strict mode

Strict mode is a language mode defined by ECMAScript that removes several error-prone legacy behaviors and turns silent failures into explicit errors. It changes how JavaScript parses and executes code: accidental assignments cannot create globals, writes that cannot succeed throw, plain function calls do not substitute the global object for this, and eval and arguments behave more predictably. It is enabled by a precise "use strict" directive in classic scripts or functions; ES modules and class bodies are already strict. Strict mode improves diagnostics, but it is not a type system, immutability system, or security sandbox.

Why strict mode exists

JavaScript originally favored permissive behavior. Some operations that usually indicate a programming mistake were silently ignored or given surprising behavior. The language could not simply change all existing scripts without breaking deployed web content, so ECMAScript defined a strict variant that code can opt into. Newer language constructs—especially modules and classes—use strict semantics by default.

Strict mode has two related purposes:

  1. Make mistakes observable. Operations that previously failed silently often throw, so defects surface near their cause.
  2. Reduce semantic ambiguity. Restrictions around scope, this, eval, and arguments let readers, tools, and engines reason about code more reliably.

It does not alter JavaScript into a statically typed language and does not make arbitrary code safe to run. It is a collection of specific grammar and runtime rules.

How code enters strict mode

ES modules

Every ECMAScript module is strict automatically:

// app.mjs, or a file loaded as an ES module
console.log((function () { return this; })()); // undefined

There is no need to add "use strict" to a module. Adding it is redundant. This applies to module code and functions defined within it. A bundler may change how source is wrapped, but correctly generated module code preserves the required semantics.

Class bodies

All code defined within a class body is strict, including constructors, methods, getters, setters, and static initialization blocks:

class Example {
  method() {
    return this;
  }
}

const method = new Example().method;
method(); // undefined in a plain call, because class methods are strict

The class being strict does not mean every function elsewhere in the surrounding classic script is strict.

Classic scripts

A classic script can opt in with a Use Strict Directive at the beginning of its script body:

"use strict";

// The rest of this classic script is strict.

The directive must be part of the script’s initial directive prologue: the sequence of initial expression statements consisting of string literals. It must use the exact string value use strict without an escape sequence or line continuation. Comments and whitespace do not break the prologue, but an ordinary statement does:

"use strict"; // directive
"another directive"; // also in the prologue

runSetup();
"use strict"; // ordinary string expression now; too late to enable strict mode

This is not a directive:

("use strict"); // Parenthesized expression, not a directive-prologue string literal

Nor is this one:

"use\\x20strict"; // Escape sequence: not the exact Use Strict Directive

In HTML, a directive in one classic <script> does not make a later classic <script> strict. Each script source unit has its own mode. A classic script can therefore contain both strict and non-strict code in separate script elements.

Function bodies

A classic-script function can enable strict mode for itself and functions nested inside it:

function processLegacyInput(input) {
  "use strict";

  function normalize(value) {
    // This nested function is strict too.
    return value.trim();
  }

  return normalize(input);
}

The function directive does not retroactively make its containing script strict, and it does not make unrelated functions strict. Strictness is associated with ECMAScript source units and function code, not a mutable global switch.

There is one syntax restriction: a function with a non-simple parameter list cannot contain its own "use strict" directive. A list is non-simple if it uses a default value, rest parameter, or destructuring:

function invalid(value = 1) {
  "use strict"; // SyntaxError
}

The function can still be strict by being defined in an already-strict module, class, or strict function. It simply cannot use a body directive with that parameter list.

Dynamic function construction

new Function(...) creates function code from strings in global scope; it does not capture the surrounding lexical scope. The generated body is not made strict merely because its creator is strict. Include a valid directive in the generated body if strict semantics are required, though dynamic code generation should usually be avoided:

const f = new Function('"use strict"; return this;');
f(); // undefined

Direct eval is different: strictness of the calling code affects direct eval, and eval code can also contain its own directive. Indirect eval runs as global code; it does not inherit a local lexical environment or strictness merely from the function that references it.

What strict mode changes

1. Undeclared assignment throws

In sloppy mode, assigning to an unresolvable identifier may create a property on the global object. In strict mode, it throws ReferenceError:

"use strict";

count = 1; // ReferenceError: count was never declared

Declare variables explicitly with const, let, or (in legacy code) var. This catches spelling mistakes such as userNmae = name. Explicitly writing globalThis.count = 1 is a property assignment, not an undeclared identifier assignment; strict mode does not prohibit intentional global-object access.

2. Failed writes throw instead of failing silently

In sloppy code, some writes that cannot take effect are silently ignored. Strict code throws TypeError, including assignments to:

"use strict";

const fixed = Object.freeze({ status: "ready" });
fixed.status = "changed"; // TypeError

"text".custom = true; // TypeError

This is useful because the program no longer appears to update state when the update did not happen. Object.freeze itself is shallow; this example is about strict assignment to a frozen own property.

3. Deletion failures are visible

Deleting an unqualified identifier such as a variable or function name is a syntax error in strict mode:

"use strict";
let temporary = 1;
delete temporary; // SyntaxError

For an object property, delete object.property can return false in sloppy code when the property is non-configurable; strict mode throws TypeError instead. Deleting a configurable property remains allowed. delete removes properties, not the binding created by const, let, or var.

"use strict";
const settings = {};
settings.temporary = true;
delete settings.temporary; // Allowed

4. Plain-call this is not replaced with the global object

In a strict traditional function, a plain call preserves the supplied this value. Calling without a receiver yields undefined; passing null preserves null; passing a primitive preserves the primitive rather than boxing it:

"use strict";

function inspectThis() {
  return this;
}

inspectThis();        // undefined
inspectThis.call(null); // null
inspectThis.call(7);    // 7

In sloppy functions, a plain call substitutes the global object for nullish this and boxes primitive receivers. Strictness does not remove normal method-call behavior: object.method() still passes object as this. Nor does it change arrow functions; arrows capture this lexically rather than deriving it from the call site.

This difference exposes detached-method bugs:

"use strict";

const counter = {
  value: 3,
  read() { return this.value; },
};

counter.read(); // 3
const read = counter.read;
read(); // TypeError when it tries to read value from undefined

Bind the method or use an explicit closure when the callback must retain a receiver.

5. with is forbidden

The with statement is a syntax error in strict code:

"use strict";
with (settings) { mode = "fast"; } // SyntaxError

with makes it ambiguous whether an identifier refers to a lexical variable or a property of the supplied object, and it interferes with predictable name resolution and optimization. Use explicit property access or destructuring instead.

6. Duplicate formal parameter names are syntax errors

Strict functions cannot declare duplicate parameter names:

"use strict";
function combine(value, value) {} // SyntaxError

This avoids ambiguity about which argument a parameter refers to. Modern functions with default, rest, or destructured parameters already disallow duplicate names even without a strict directive.

7. Legacy octal syntax is rejected

Leading-zero legacy octal numeric literals and octal escape sequences in strings are syntax errors in strict code. Use explicit modern notation when octal is intended:

"use strict";
const permissions = 0o755; // Explicit octal literal
// const invalid = 0755;    // SyntaxError in strict mode

The prohibition prevents literals such as 010 from being misread as decimal ten when they are interpreted as octal eight.

8. eval cannot introduce local bindings into its caller

In sloppy direct eval, declarations made by evaluated code can affect the surrounding variable environment. Strict direct eval has its own variable environment, so declarations inside it do not become local bindings of the caller:

"use strict";
eval("var hidden = 1");
typeof hidden; // "undefined"

Strict eval also does not allow code to delete or rebind caller variables through its special legacy interactions. This improves analyzability, but eval remains powerful, hard to audit, and dangerous with untrusted strings. Strict mode does not make eval safe.

9. arguments is simpler and independent from parameters

In sloppy functions, named parameters and indexed properties of arguments can be linked (the “mapped arguments” behavior). In strict functions, they are independent:

function example(value) {
  "use strict";
  value = 9;
  return [value, arguments[0]];
}

example(2); // [9, 2]

Strict arguments objects also do not expose the legacy callee and caller access patterns; accessing restricted properties can throw. Prefer rest parameters for APIs that intentionally accept a variable number of arguments.

10. Some bindings and assignments involving eval and arguments are forbidden

Strict code rejects using eval or arguments as binding identifiers or assignment targets in places where the grammar prohibits them:

"use strict";
let eval;          // SyntaxError
arguments = [];    // SyntaxError
function f(arguments) {} // SyntaxError

This rule does not make the words unusable everywhere: object.eval is a property access, and calling the built-in eval is still possible. The restrictions avoid legacy aliasing and binding behavior.

11. Some legacy reflective properties are restricted

Strict functions do not provide the old function.caller and function.arguments introspection behavior; accessing these restricted properties throws. This prevents code from inspecting caller frames through those legacy properties and simplifies function semantics.

What strict mode does not change

Strict mode is often over-attributed. It does not:

Strict mode versus modules, classes, TypeScript, and linting

Mechanism What it means
Strict mode ECMAScript parsing/runtime rules described in this page.
ES module A module source unit; it is always strict and has module scope.
Class body Class constructors and methods are strict automatically.
TypeScript strict A compiler family of static type-checking options; it is not the same as JavaScript strict mode. Emitted JavaScript may have its own module/script mode.
ESLint rules Static checks configured by a project; they can catch many mistakes but do not alter runtime semantics.

In legacy projects, a file named .js is not necessarily a module. Its actual loader and bundler configuration determine whether it is parsed as a module. Check the execution environment rather than assuming the extension enables module semantics.

Debugging strict-mode errors

When strict mode reveals a failure:

  1. Locate the actual source unit. Determine whether the code is a classic script, ES module, class method, function body, eval string, or generated function.
  2. Read the error category. Syntax errors occur before execution (for example, with or delete identifier); runtime ReferenceError and TypeError indicate executed code.
  3. Look for silent legacy assumptions. Common causes include misspelled undeclared names, assignment to read-only objects, detached methods, legacy octal, or code mutating primitives.
  4. Fix the assumption rather than suppressing strict mode. Declare the binding, pass the receiver, update the correct state owner, or replace obsolete syntax.
  5. Check wrappers and build output. A bundler may turn files into modules, function wrappers, or concatenated scripts; directive placement can change whether a string is actually a directive.

Strict-mode errors often reveal pre-existing defects that sloppy execution hid. Flipping an entire legacy bundle to strict mode can therefore cause new visible failures, which is why migration should be done incrementally with tests and clear script boundaries.

Migration guidance for legacy code

Common interview questions

What is strict mode?

An ECMAScript language mode that changes specified syntax and runtime behavior to reject certain error-prone legacy constructs and make failures more visible.

How do you enable it?

Use the exact "use strict" directive in a classic script’s or function body’s directive prologue. ES modules and class bodies are strict automatically.

Does strict mode apply to an entire application?

No. It applies to source units and function code under the specification. Separate classic script elements can have different modes; module code is strict independently.

Why does a plain function have this === undefined?

Strict functions preserve the receiver supplied by the call. A plain call supplies no receiver, so this remains undefined instead of being replaced with the global object.

Does strict mode make this undefined in methods?

No. obj.method() still passes obj as the receiver. Strict mode changes substitution for plain calls and primitive boxing, not method-call syntax.

Why does undeclared assignment throw?

Strict mode rejects assignment to an unresolvable identifier rather than silently creating a global-object property, catching typos and accidental global state.

Can strict mode be added to a function with default parameters?

Not by a body directive: a "use strict" directive in a function with a non-simple parameter list is a syntax error. The function may be strict by inheritance from a module, class, or strict outer code.

Does strict mode disable eval?

No. It constrains eval’s legacy scope behavior; it does not remove eval or make untrusted code safe.

Does strict mode make JavaScript statically typed?

No. It changes runtime and syntax rules. TypeScript compiler strictness and lint rules are separate mechanisms.

What is the safest default for new code?

Use ES modules and modern language constructs; they are strict automatically. Avoid relying on sloppy-mode behavior in legacy scripts.

Summary checklist

References