cf.completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Sloppy mode in depth

See what JavaScript does without "use strict": this boxing, implicit globals, mapped arguments, arguments.callee, duplicate parameters and silent failures.

By the end, you can
  • 01
    Tell which code is sloppySay whether a script, a function, a new Function body, or a module runs in sloppy or strict mode.
  • 02
    Predict what sloppy code doesExplain boxed this, implicit globals, mapped arguments, arguments.callee, duplicate parameters, and writes that fail with no message.
  • 03
    Move old code to strict modeFind the lines that change or throw under "use strict", and rewrite them so the code works in both modes.

Sloppy mode: JavaScript without "use strict"

JavaScript can run code in two modes. Strict mode is the one you turn on with "use strict", and modules and classes always use it. The other mode is what you get when nothing turns strict mode on. People call it sloppy mode. The spec, the rulebook for JavaScript, just calls it "non-strict code".

The strict mode lesson shows how to turn strict mode on and what it forbids. This lesson looks at the other side: what JavaScript really does when strict mode is off. A lot of old code on the web runs this way, so you will meet these rules when you read or fix it.

Here is a tiny script with a typo. It runs in sloppy mode, because nothing in it says "use strict".

A typo in an assignment, in sloppy modePop out in the code editor (opens in a new tab)JavaScript
function save(price) {
  totl = price;
}
save(3);
console.log(totl);

Lines 1 to 3 make a function called save. Line 2 was meant to store the price in a variable called total, but it says totl. No variable called totl exists anywhere.

Line 4 calls save(3). In sloppy mode, line 2 doesn't stop with an error. JavaScript guesses that you wanted a new global variable, and makes one called totl. Line 5 finds it and prints 3. The typo hides, and the program keeps going.

Now the same code with "use strict" on a new first line:

The same typo, in strict modePop out in the code editor (opens in a new tab)JavaScript
"use strict";
function save(price) {
  totl = price;
}
try {
  save(3);
} catch (error) {
  console.log(error.name);
}

Line 1 turns on strict mode for the whole script. Lines 2 to 4 are the same save function, so the typo is now on line 3.

Line 6 calls save(3) inside a try block. In strict mode, line 3 throws a ReferenceError, the error for a name that doesn't exist. Lines 7 and 8 catch it and print its name, ReferenceError. Now the typo is reported at the line where it happened.

The same code runs twice. In sloppy mode, the line totl = price makes a global named totl and the script prints 3. In strict mode, that line throws a ReferenceError.the scripttotl = price;sloppymakes a global totlprints 3strictthe assignment throwsReferenceError
The same code, two modes. Sloppy mode guesses that totl should be a new global, so the script prints 3. Strict mode stops at totl = price with a ReferenceError.

Why does sloppy mode still exist? Websites written years ago still run today, and many of them depend on these old rules. If browsers changed the rules for code without "use strict", those pages would break. So the rules stay, and strict mode is something code must ask for.

Real-life analogyA clerk who never asks questions

Picture a clerk who fills in your order form for you. When something is unclear, the clerk never asks. The clerk guesses and carries on. Most orders still come out right. But when a guess is wrong, you only find out much later, when the wrong parcel arrives.

In real life: You write "tee" on the order form, and the clerk adds a new item called "tee"
In JavaScript: totl = price makes a new global called totl
In real life: You ask the clerk to change a sealed parcel, and the clerk nods and does nothing
In JavaScript: A write to a frozen object does nothing, with no message
In real life: A careful clerk hands the form back and points at the mistake
In JavaScript: Strict mode throws an error at that line
The main idea

Sloppy mode is how JavaScript runs code that isn't strict: a classic script, or a function, with no "use strict". Most code works the same in both modes. In a few places, sloppy mode quietly guesses or skips a step instead of throwing an error. This lesson goes through each of those places: this, globals, arguments, arguments.callee, duplicate parameters, and writes that fail.

Which code runs in sloppy mode

Sloppy mode is the default. A piece of code runs in strict mode only if something makes it strict. Each script and each function picks its mode from its own text, or from the code it is written inside.

The table below lists the common places code can live. Read each row as: "code written here runs in this mode."

Which mode each kind of code runs in
Where the code is writtenWhich mode it runs in
A classic <script> with no "use strict" at the topSloppy mode.
A function inside that script, with no "use strict" of its ownSloppy mode, like the script around it.
A function whose body starts with "use strict"Strict mode, even inside a sloppy script.
A <script type="module">, or a file loaded with importStrict mode, always.
The body of a classStrict mode, always.
The body of new Function("…")Sloppy mode, unless the body starts with "use strict". The code around it doesn't count.
An HTML handler such as onclick="…"Sloppy mode, unless its text starts with "use strict".

You can check the mode from inside a function. In a plain call like check(), with nothing before the dot, strict mode gives the function undefined as this. Sloppy mode never does. So this === undefined is true only in strict mode.

One script, one sloppy function and one strict functionPop out in the code editor (opens in a new tab)JavaScript
function looseCheck() {
  return this === undefined;
}
function strictCheck() {
  "use strict";
  return this === undefined;
}
console.log(looseCheck());
console.log(strictCheck());

The script has no "use strict" at the top, so it is sloppy. Lines 1 to 3 make looseCheck. It has no directive of its own, so it is sloppy too.

Lines 4 to 7 make strictCheck. Line 5 is "use strict" at the very top of its body, so this one function is strict, even inside a sloppy script.

Line 8 prints false: looseCheck doesn't get undefined as this. Line 9 prints true: strictCheck does.

One case surprises many people. new Function("…") builds a function from text. That text picks its own mode. The code around it doesn't count.

new Function inside a strict scriptPop out in the code editor (opens in a new tab)JavaScript
"use strict";
const loose = new Function("return this === undefined;");
const strict = new Function('"use strict"; return this === undefined;');
console.log(loose());
console.log(strict());

Line 1 makes the whole script strict. Line 2 builds loose from the text "return this === undefined;". That text has no directive, so loose is sloppy.

Line 3 builds strict from text that starts with "use strict", so strict is strict. Line 4 prints false and line 5 prints true. The same holds inside a module: a new Function body is sloppy unless its own text says otherwise.

this in sloppy mode: the global object and boxed values

Inside a function, this is the value the function was called on. In cart.show(), this is cart. You can also choose this yourself with .call(): show.call(cart) runs show with cart as this.

Strict mode uses whatever value it is given, even undefined. Sloppy mode first changes the value in two cases:

  • If the value is undefined or null, sloppy mode uses the global object instead. That is the object that holds the global variables. In a browser it is window, and every environment also names it globalThis.
  • If the value is a primitive, such as a number, a string or a boolean, sloppy mode wraps it in an object. This is called boxing.
this in a plain call is the global objectPop out in the code editor (opens in a new tab)JavaScript
function whoIsThis() {
  return this;
}
console.log(whoIsThis() === globalThis);
console.log(whoIsThis.call(null) === globalThis);
console.log(whoIsThis.call(undefined) === globalThis);

Lines 1 to 3 make whoIsThis, a sloppy function that returns its this.

Line 4 calls it with nothing before the dot, so there is no this to give. Sloppy mode puts the global object in its place, and line 4 prints true. Lines 5 and 6 hand in null and undefined on purpose. Sloppy mode swaps both for the global object, so both lines print true too.

In strict mode, all three lines would print false: this would be undefined, null and undefined. This is why a sloppy function that writes this.price = 3 after a plain call quietly makes a global called price.

Now the second case. What happens when the value handed in is the number 3?

A number handed in as this gets boxedPop out in the code editor (opens in a new tab)JavaScript
function getThis() {
  return this;
}
const boxed = getThis.call(3);
console.log(typeof boxed);
console.log(boxed instanceof Number);
console.log(boxed + 1);
console.log(boxed === getThis.call(3));

Lines 1 to 3 make getThis, which returns its this. Line 4 calls it with .call(3), so 3 is handed in as this, and saves the result in boxed.

In sloppy mode, this must be an object. So JavaScript makes a new Number object, a box with the number 3 inside, and line 2 returns that box.

  • Line 5 prints object. typeof gives the kind of a value, and a box is an object, not a number.
  • Line 6 prints true. instanceof Number checks that the box was made by Number.
  • Line 7 prints 4. The + takes the number out of the box first, so math still works.
  • Line 8 prints false. The second call makes a second, new box. === compares two objects by which object they are, not by what is inside them.
getThis.call(3) in two modes. In sloppy mode, the this tag points at a box labeled Number with 3 inside. In strict mode, the this tag points at the number 3 itself.sloppy modethis3Numbera new box around 3strict modethis3the number 3 itself
Line 4 hands in 3 as this. Sloppy mode wraps it in a new Number object, so line 5 prints object. Strict mode keeps the number, so line 5 would print number.
Step through a number handed in as this
Step 0 of 9Ready
Your turn: follow the blue line

A replay of the example code, not a view inside a JavaScript engine. getThis runs for real, once in sloppy mode and once in strict mode.

Running in
  1. script
Next: line 1
  return this;}const boxed = getThis.call(3);console.log(typeof boxed);console.log(boxed instanceof Number);console.log(boxed + 1);console.log(boxed === getThis.call(3));
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Press Step to follow the call. Watch line 2: the value handed in is 3, but the value returned is a box. The last step runs the same function in strict mode, where the number comes back as it went in. This replays the example code; it isn't a view inside the JavaScript engine.

Boxing matters most in methods added to a built-in type. Inside a sloppy method on String.prototype, this is a String object, not the string itself. So this === "tea" can never be true there. You will fix one in the exercises.

Arrow functions don't follow either rule. An arrow function has no this of its own. It uses the this of the code around it, in both modes.

Implicit globals: a name nobody declared

To declare a variable means to create it with let, const, var, function or class. When code assigns to a name that was never declared anywhere, sloppy mode doesn't throw. It adds a new property with that name to the global object. This is called an implicit global: a global variable that nobody declared on purpose.

The typo in the overview made one. Here is a quieter way to make one, which looks like it declares two variables but declares only one.

A chained assignment leaks a globalPop out in the code editor (opens in a new tab)JavaScript
function setup() {
  let price = tax = 3;
  return price;
}
setup();
console.log(tax);
console.log(typeof price);

Lines 1 to 4 make setup. Line 2 runs from right to left. First tax = 3 runs. It is a plain assignment, and tax was never declared. Its result, 3, then goes into price. The let belongs to price only.

So price is a normal variable that lives inside setup. But tax becomes a property of the global object.

Line 5 calls setup(). Line 6 prints 3: tax is still there after the function has finished, and any code on the page can read it. Line 7 prints undefined: price stayed inside setup, and typeof gives "undefined" for a name that doesn't exist.

Line 2, let price = tax = 3, inside setup. price is declared, so it stays in the box for setup. tax was never declared, so sloppy mode adds tax: 3 to the global object, drawn as a cart.let price = tax = 3;price: 3setupdeclared, stays insidetax: 3globalnever declared, new global
Line 2 declares only price. Nothing declares tax, so sloppy mode adds it to the global object. That is why line 6 prints 3 and line 7 prints undefined.

Sloppy mode makes a global only when code assigns to a missing name. Reading a missing name is an error in both modes.

Reading a missing name throws, even in sloppy modePop out in the code editor (opens in a new tab)JavaScript
console.log(typeof cake);
try {
  console.log(cake);
} catch (error) {
  console.log(error.name);
}
cake = 4;
console.log(cake);

Line 1 prints undefined. typeof is the one way to ask about a missing name without an error.

Line 3 reads cake, which doesn't exist yet, so it throws a ReferenceError. Lines 4 and 5 catch it and print ReferenceError.

Line 7 assigns 4 to cake. That makes an implicit global, so line 8 prints 4.

An implicit global is not quite the same as a global made with var. A var at the top level of a script also adds a property to the global object, but that property can't be removed. An implicit global is a plain property, so delete can remove it.

delete removes an implicit global but not a varPop out in the code editor (opens in a new tab)JavaScript
var tea = 3;
cake = 4;
console.log(delete globalThis.tea);
console.log(delete globalThis.cake);
console.log(typeof tea, typeof cake);

This example must run as a top-level script, as it does here. Inside a function, var tea would make a local variable, not a global one.

Line 1 declares tea with var. Line 2 makes cake as an implicit global. Both become properties of the global object.

Line 3 prints false: the tea property is locked in place, so delete can't remove it. Line 4 prints true: cake is removed. Line 5 prints number undefined: tea still holds 3, and cake is gone.

Implicit globals cause hard bugs. Two scripts on one page can share a name by accident, and a loop counter written without let can change a counter in another loop. Strict mode throws a ReferenceError at the assignment instead.

Mapped arguments: two names for one slot

Every normal function (not an arrow function) gets an object called arguments. It holds the values the function was called with: arguments[0] is the first one, arguments[1] the second, and arguments.length says how many there are.

In a sloppy function with plain parameters, arguments has a strange extra rule. Each parameter and its arguments entry share one slot. Change one, and the other changes too. The spec calls this a mapped arguments object, because each entry is mapped to a parameter.

price and arguments[0] change togetherPop out in the code editor (opens in a new tab)JavaScript
function order(price) {
  arguments[0] = 4;
  console.log(price);
  price = 5;
  console.log(arguments[0]);
}
order(3);

Lines 1 to 6 make order, with one parameter, price. Line 7 calls order(3), so price and arguments[0] both start as 3.

Line 2 writes 4 into arguments[0]. It never touches price, yet line 3 prints 4. The two names use the same slot.

Line 4 gives price the value 5. Line 5 prints 5: arguments[0] changed as well. The link works in both directions.

In strict mode, the same code prints 3 and then 4. A strict arguments object is unmapped: it is a plain copy of the values at the start of the call.

The values after line 2. In sloppy mode, price and arguments[0] both point at one slot holding 4. In strict mode, price points at 3 and arguments[0] points at a separate 4.sloppy modepricearguments[0]4one slotstrict modepricearguments[0]34two slots
After line 2 writes 4 into arguments[0]. In sloppy mode both names share one slot, so price is 4 too. In strict mode they are two slots, and price stays 3.
Step through price and arguments[0] sharing a slot
Step 0 of 7Ready
Your turn: follow the blue line

A replay of the example code, not a view inside a JavaScript engine. order runs for real, once in sloppy mode and once in strict mode.

Running in
  1. script
Next: line 7
function order(price) {  arguments[0] = 4;  console.log(price);  price = 5;  console.log(arguments[0]);}
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Press Step and watch the two names in the side panel. After line 2, both show 4. After line 4, both show 5. The last step runs order in strict mode for comparison. This replays the example code; it isn't a view inside the JavaScript engine.

The link exists only in a sloppy function whose parameters are all plain names. A default value, a rest parameter (...prices), or destructuring ({ size }) turns it off, even in sloppy mode.

A default value turns the link offPop out in the code editor (opens in a new tab)JavaScript
function order(price = 0) {
  arguments[0] = 4;
  console.log(price);
}
order(3);

Line 1 gives price a default value, = 0. That makes arguments unmapped, the strict kind, even though the code is sloppy. Line 2 writes 4 into arguments[0], and line 3 prints 3: price didn't change.

The link also needs an argument to link to. When the caller passes nothing, there is no slot to share.

No argument, no linkPop out in the code editor (opens in a new tab)JavaScript
function order(price) {
  arguments[0] = 4;
  console.log(price);
}
order();

Line 5 calls order() with no arguments. Line 2 still writes 4 into arguments[0], but that entry isn't linked to price. Line 3 prints undefined.

Mapped arguments make code hard to read: a line that seems to change only arguments also changes a parameter. If you need the values as a list, use a rest parameter instead.

arguments.callee and caller

In sloppy mode, arguments has one more property: arguments.callee, the function that is running right now. Old code used it to let a function with no name call itself.

A function with no name calls itselfPop out in the code editor (opens in a new tab)JavaScript
const countdown = function (n) {
  if (n === 0) return "go";
  return arguments.callee(n - 1);
};
console.log(countdown(3));

Lines 1 to 4 save a function with no name in countdown. Line 2 returns "go" when n reaches 0. Line 3 calls arguments.callee, which is this same function, with n - 1.

Line 5 starts at 3. The function calls itself with 2, then 1, then 0, and the 0 call returns "go". So line 5 prints go.

In strict mode, reading arguments.callee throws a TypeError. The modern way is to give the function expression its own name.

A named function expression calls itself in strict modePop out in the code editor (opens in a new tab)JavaScript
"use strict";
const countdown = function step(n) {
  if (n === 0) return "go";
  return step(n - 1);
};
console.log(countdown(3));

Line 1 makes the script strict. Line 2 names the function step. That name works only inside the function itself, so it can't clash with other code. Line 4 calls step(n - 1) instead of arguments.callee, and line 6 prints go, in either mode.

arguments.callee belongs to the mapped arguments object. An unmapped one, such as in a function with a default value, throws on callee even in sloppy mode.

arguments.callee throws with a default valuePop out in the code editor (opens in a new tab)JavaScript
function countdown(n = 3) {
  try {
    return arguments.callee;
  } catch (error) {
    return error.name;
  }
}
console.log(countdown());

Line 1 gives n a default value, so arguments is unmapped. Line 3 reads arguments.callee, which throws a TypeError. Line 4 catches it, line 5 returns its name, and line 8 prints TypeError.

The second old feature is caller. A sloppy function has a caller property that gives the function that called it. The spec doesn't define it for sloppy functions; browsers keep it so old pages still work.

pay.caller names the function that called payPop out in the code editor (opens in a new tab)JavaScript
function checkout() {
  return pay();
}
function pay() {
  return pay.caller === checkout;
}
console.log(checkout());

Lines 1 to 3 make checkout, which calls pay on line 2. Lines 4 to 6 make pay. Line 5 reads pay.caller, the function that called pay, and checks that it is checkout. Line 7 prints true.

For a strict function, reading caller throws a TypeError. Both features let code reach functions it was never handed, and they make it harder for engines to speed code up. So strict mode turns them off. To call a function, pass it in or give it a name.

Two parameters with the same name

A parameter is a name in the brackets of a function, such as price in function add(price). Sloppy mode lets a plain function use the same parameter name twice. Strict mode doesn't.

Two parameters called pricePop out in the code editor (opens in a new tab)JavaScript
function add(price, price) {
  console.log(arguments[0], arguments[1]);
  return price;
}
console.log(add(3, 4));

Line 1 makes add with two parameters, both called price. Line 5 calls add(3, 4).

Line 2 prints 3 4. Both values arrived, and arguments still holds each one. But the name price can hold only one value. The last parameter with that name wins, so line 3 returns 4, and line 5 prints 4. The first value, 3, can only be reached through arguments[0].

In strict mode, line 1 is a SyntaxError. The engine finds it while reading the code, so no line of the script runs at all.

Even sloppy mode allows a repeated name only in a plain function with plain parameters. Newer kinds of function never allowed it, in either mode:

  • arrow functions,
  • functions with a default value, a rest parameter, or destructuring,
  • methods in a class, which are always strict anyway.
An arrow function can't repeat a parameter, even in sloppy modePop out in the code editor (opens in a new tab)JavaScript
const add = (price, price) => price;
console.log(add(3, 4));

Line 1 makes an arrow function with two parameters called price. The engine throws a SyntaxError before any code runs, so line 2 never prints.

A repeated name is almost always a typo, such as writing price where you meant cost. Give every parameter its own name.

Silent failures: writes that do nothing

Some writes can't happen. For example, Object.freeze(cup) locks cup: its properties can't change, and it can't get new ones. When code tries such a write, strict mode throws a TypeError. Sloppy mode skips the write and says nothing. This is called a silent failure.

Writes to a frozen cup do nothingPop out in the code editor (opens in a new tab)JavaScript
const cup = Object.freeze({ size: 3 });
cup.size = 4;
cup.lid = true;
console.log(delete cup.size);
console.log(cup.size, cup.lid);

Line 1 makes a cup with a size of 3 and freezes it.

  • Line 2 tries to change size to 4. Nothing happens, and there is no message.
  • Line 3 tries to add a lid property. Nothing happens again.
  • Line 4 tries to remove size. delete can't, and prints false.
  • Line 5 prints 3 undefined: size is still 3, and lid was never added.

In strict mode, line 2 would throw a TypeError, and the program would stop there.

The table below lists the writes that fail silently in sloppy mode. Read each row as: "when code tries this, sloppy mode skips it because of this." Strict mode throws a TypeError for every row.

Writes that sloppy mode skips with no message
What the code triesWhy it can't happen
Change a read-only property, like size on a frozen cupThe property can't be written.
Add a property to a frozen objectThe object can't get new properties.
Set a property that has only a getterThere is no setter to call.
Set a property on a string, like tea.size = 3A string, number, or boolean can't hold its own properties.
Write to undefined, NaN, or InfinityThese globals are read-only.
delete cup.size on a frozen cupThe property can't be removed, so delete gives false.

The string row surprises many people. A string can't hold properties of its own.

A property set on a string disappearsPop out in the code editor (opens in a new tab)JavaScript
const tea = "green";
tea.size = 3;
console.log(tea.size);

Line 1 stores the string "green" in tea. Line 2 tries to give it a size of 3. Sloppy mode skips the write, so the string stays as it was. Line 3 reads tea.size and prints undefined.

A getter is a property that runs a function when you read it. A property with only a getter has no way to store a new value.

Writing to a property that only has a getterPop out in the code editor (opens in a new tab)JavaScript
const shop = {
  get price() {
    return 3;
  },
};
shop.price = 4;
console.log(shop.price);

Lines 1 to 5 make shop. Lines 2 to 4 make price a getter that always returns 3. There is no setter, the function that would handle a write.

Line 6 tries to set price to 4. With no setter to call, sloppy mode skips it. Line 7 prints 3.

If you can't use strict mode but need to know whether a write worked, use Reflect.set and Reflect.deleteProperty. They never throw for this. They return true or false, in both modes.

Reflect reports whether a write workedPop out in the code editor (opens in a new tab)JavaScript
const cup = Object.freeze({ size: 3 });
console.log(Reflect.set(cup, "size", 4));
console.log(Reflect.deleteProperty(cup, "size"));
console.log(cup.size);

Line 1 makes a frozen cup again. Line 2 tries to set size to 4 and prints false: the write didn't happen. Line 3 tries to remove size and prints false. Line 4 prints 3.

Moving old sloppy code to strict mode

You will most often meet sloppy mode in old code: a script added to a page long ago, a library from before modules, or a file someone wants to turn into a module. A module is always strict. So turning an old script into a module, or adding "use strict" to it, can change what it does.

The table below collects every difference from this lesson. Read each row as: "when the code does this, sloppy mode does one thing and strict mode does the other."

The same code in sloppy mode and in strict mode
When the code doesWhat sloppy mode doesWhat strict mode does
totl = 3 with no declarationMakes a new property totl on the global object.Throws a ReferenceError.
whoIsThis(), a plain callthis is the global object.this is undefined.
getThis.call(3)this is a new Number object that holds 3.this is the number 3.
arguments[0] = 4The first parameter changes to 4 too.Only arguments[0] changes.
arguments.calleeGives the function that is running.Throws a TypeError.
function add(price, price)Allowed. The last price wins.A SyntaxError before any code runs.
cup.size = 4 on a frozen cupDoes nothing and shows no message.Throws a TypeError.

Here is a small function written the old way. It works in sloppy mode, but it leans on two sloppy rules.

An old sum function that relies on sloppy modePop out in the code editor (opens in a new tab)JavaScript
function sum() {
  var total = 0;
  for (i = 0; i < arguments.length; i++) {
    total += arguments[i];
  }
  return total;
}
console.log(sum(3, 4, 5));
console.log(typeof i);

Lines 1 to 7 make sum. It has no parameters, and reads its values from arguments instead. Line 3 starts a loop with i = 0, but i is never declared, so it becomes an implicit global. Line 4 adds each value to total.

Line 8 prints 12, which is 3 + 4 + 5. Line 9 prints number: the loop counter i leaked out of the function and is now a global. Under "use strict", line 3 would throw a ReferenceError.

The same sum function, safe in strict modePop out in the code editor (opens in a new tab)JavaScript
"use strict";
function sum(...prices) {
  let total = 0;
  for (const price of prices) {
    total += price;
  }
  return total;
}
console.log(sum(3, 4, 5));
console.log(typeof i);

Line 1 makes the script strict. Line 2 uses a rest parameter, ...prices, which collects every value into a real array. That replaces arguments. Line 3 declares total with let. Line 4 loops with for...of and declares price with const, so there is no counter to leak.

Line 9 prints 12, as before. Line 10 prints undefined: no global i exists. The new version gives the same result in both modes.

To move a file to strict mode safely, work through these steps:

  1. Turn strict mode on for one function or one file at a time, not for everything at once.
  2. Declare every variable with let, const or var. Search for names that are assigned but never declared.
  3. Replace arguments with rest parameters, and arguments.callee with a named function.
  4. If a function uses this after a plain call to reach the global object, write globalThis instead.
  5. Run the code and its tests. Every new TypeError points at a write that never worked.

Be careful when files are joined into one. If the first file starts with "use strict", the whole joined file becomes strict, including old files that expected sloppy mode.

Now run each difference in both modes. Pick a snippet below. The playground runs it once as written, in sloppy mode, and once with "use strict" on a new first line. For each one, guess both results before you look.

Playground: run one snippet in both modes
A typo in an assignmentPop out in the code editor (opens in a new tab)JavaScript
function save(price) {
  totl = price;
}
save(3);
console.log(totl);
What each run givesdifferent
Sloppy run, as written
prints3
Strict run, with "use strict" on top
throwsReferenceError
Try it yourself
The same code, two modes.

Sloppy mode runs this code and guesses what you meant. Strict mode stops with a ReferenceError instead.

Both runs are real. This page runs the snippet as the body of new Function: once as written, which is sloppy, and once with "use strict" put on a new first line.
Does strict mode change this code?
  • let price = 3; price = 4;
  • count = 1; with no declaration anywhere
  • arguments.length inside a function
  • arguments[0] = 4 in function order(price)
  • arguments.callee(n - 1)
  • this inside an arrow function
  • cup.size = 4 after Object.freeze(cup)
  • Reflect.set(cup, "size", 4) on a frozen cup
Try it yourself
0 of 8 correct

Put each line in its group: the ones that give the same result in both modes, and the ones that change or throw in strict mode.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Common mix-ups

Most mix-ups about sloppy mode come from expecting it to act like strict mode, or from thinking it is something code switches on.

  • "Sloppy mode is a setting you switch on." No. It is the default. Code is sloppy unless "use strict", a module, or a class makes it strict.
  • "Code in a module is sloppy if I leave out "use strict"." No. Modules and class bodies are always strict.
  • "In sloppy mode, this in a plain call is undefined." No. Sloppy mode swaps undefined and null for the global object, and boxes numbers, strings and booleans.
  • "Reading a name nobody declared gives undefined in sloppy mode." No. Reading it throws a ReferenceError in both modes. Only an assignment makes an implicit global.
  • "arguments always follows the parameters." No. Only in a sloppy function with plain parameters, and only for arguments that were really passed.
  • "A write that showed no error must have worked." No. Sloppy mode skips some writes with no message. Check with Reflect.set, or run the code in strict mode.
  • "new Function inside strict code makes a strict function." No. Its body is sloppy unless its own text starts with "use strict".

The table below puts the two modes and the directive side by side. Read each row as: "this does that, but it doesn't do this other thing."

What sloppy mode, strict mode, and the directive do and don't do
TermWhat it doesWhat it doesn't do
Sloppy modeRuns old code the way it always ran.It isn't a different language. Most code gives the same results in both modes.
Strict modeTurns many quiet guesses into errors.It doesn't fix bugs for you. It only reports them, and you still fix them.
"use strict" on an old fileMakes the file follow the stricter rules.It can break code that relied on sloppy rules, so test before you ship it.

Practice exercises

Every program below is a classic script with no "use strict" unless its first line says so. Guess the result before you run it. Then use the hints if you get stuck.

Exercise 1 · Warm-upPredict the type of a boxed this

kind returns typeof this. Line 4 calls it with the string "tea" as this. What does line 4 print?

Starter codePop out in the code editor (opens in a new tab)JavaScript
function kind() {
  return typeof this;
}
console.log(kind.call("tea"));

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upFind the leaked global

    Line 2 seems to declare two variables. What does line 6 print? Then change line 2 so that nothing leaks out of start.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    function start() {
      var cups = mugs = 2;
      return cups;
    }
    start();
    console.log(typeof mugs);
    console.log(typeof cups);

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticePredict arguments with a default value

      refill is sloppy. Line 2 changes cups, and line 3 returns arguments[0]. What does line 5 print?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      function refill(cups, size = 1) {
        cups = 5;
        return arguments[0];
      }
      console.log(refill(3));

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeFix a string method that never matches

        Lines 1 to 3 add an isTea method to every string. What does line 4 print? Then change one line so it prints true.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        String.prototype.isTea = function () {
          return this === "tea";
        };
        console.log("tea".isTea());

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengePredict a write through arguments with two size parameters

          pick has two parameters called size, and line 2 writes 9 into arguments[0]. What does line 5 print?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          function pick(size, size) {
            arguments[0] = 9;
            return size;
          }
          console.log(pick(3, 4));

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengeMake an old function work in strict mode

            This file starts with "use strict", and total was written for sloppy mode, so it throws. Fix lines 2 and 4 so it runs in strict mode. What does your fixed program print?

            Starter codePop out in the code editor (opens in a new tab)JavaScript
            "use strict";
            const total = function (n) {
              if (n === 0) return 0;
              sum = n + arguments.callee(n - 1);
              return sum;
            };
            console.log(total(3));

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              For each question, ask: is this code sloppy or strict, and which sloppy rule applies? Each answer explains why it's right or wrong.

              The sloppy mode quiz · 8 questionsScore: first tries count
              1. Question 1 of 8In sloppy mode, what is this inside a plain call like whoIsThis()?

                Choose an answer to see the explanation.

              2. Question 2 of 8What does this comparison of a boxed this with 3 print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                function getThis() {
                  return this;
                }
                console.log(getThis.call(3) === 3);

                Choose an answer to see the explanation.

              3. Question 3 of 8What does this program, which calls pour twice, print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                function pour(cups) {
                  cups = 4;
                  console.log(arguments[0]);
                }
                pour(3);
                pour();

                Choose an answer to see the explanation.

              4. Question 4 of 8Why does arguments.callee throw a TypeError in function countdown(n = 3), even in sloppy mode?

                Choose an answer to see the explanation.

              5. Question 5 of 8What does deleting a var global and an implicit global print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                var cups = 3;
                mugs = 4;
                console.log(delete globalThis.cups, delete globalThis.mugs);

                Choose an answer to see the explanation.

              6. Question 6 of 8Which line is a SyntaxError before any code runs, even in sloppy mode?

                Choose an answer to see the explanation.

              7. Question 7 of 8What does this write to a frozen cup print in sloppy mode?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const cup = Object.freeze({ size: 3 });
                cup.size = 4;
                console.log(cup.size);

                Choose an answer to see the explanation.

              8. Question 8 of 8Which of these runs in sloppy mode?

                Choose an answer to see the explanation.

              Key takeaways

              Sloppy mode is old JavaScript that guesses instead of throwing. Knowing its rules lets you read old code and move it to strict mode safely.

              • Code is sloppy unless "use strict", a module, or a class makes it strict. A new Function body picks its mode from its own text.
              • In a sloppy function, undefined or null as this becomes the global object, and a number, string or boolean is boxed in an object.
              • Assigning to a name nobody declared makes an implicit global. Reading such a name still throws a ReferenceError.
              • In a sloppy function with plain parameters, each parameter shares a slot with its arguments entry. Default values, rest parameters and destructuring turn that off.
              • arguments.callee and a function's caller work only in sloppy code. Use a named function instead.
              • Sloppy mode allows two parameters with the same name in a plain function, and the last one wins. Strict mode makes it a SyntaxError.
              • Writes that can't happen fail silently in sloppy mode and throw a TypeError in strict mode. Reflect.set reports them in both modes.

              Remember the one-liner.
              Sloppy mode guesses and keeps going; strict mode stops and tells you.

              Coming next: The with statement. It looks at one more old sloppy feature, with, which changes how names are looked up and is banned in strict mode.

              CompleteFrontend Clear concepts. Working examples.