Right shift (>>)

Right shift (>>)
Baseline
Widely available
This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015.
The right shift (>>
) operator returns a number or BigInt whose binary representation is the first operand shifted by the specified number of bits to the right. Excess bits shifted off to the right are discarded, and copies of the leftmost bit are shifted in from the left. This operation is also called "sign-propagating right shift" or "arithmetic right shift", because the sign of the resulting number is the same as the sign of the first operand.
Try it
const a = 5; // 00000000000000000000000000000101
const b = 2; // 00000000000000000000000000000010
const c = -5; // 11111111111111111111111111111011
console.log(a >> b); // 00000000000000000000000000000001
// Expected output: 1
console.log(c >> b); // 11111111111111111111111111111110
// Expected output: -2
Syntax
x >> y
Description
The >>
operator is overloaded for two types of operands: number and BigInt. For numbers, the operator returns a 32-bit integer. For BigInts, the operator returns a BigInt. It first coerces both operands to numeric values and tests the types of them. It performs BigInt right shift if both operands become BigInts; otherwise, it converts both operands to 32-bit integers and performs number right shift. A TypeError
is thrown if one operand becomes a BigInt but the other becomes a number.
Since the new leftmost bit has the same value as the previous leftmost bit, the sign bit (the leftmost bit) does not change. Hence the name "sign-propagating".
The operator operates on the left operand's bit representation in two's complement. Consider the 32-bit binary representations of the decimal (base 10) numbers 9
and -9
:
9 (base 10): 00000000000000000000000000001001 (base 2) -9 (base 10): 11111111111111111111111111110111 (base 2)
The binary representation under two's complement of the negative decimal (base 10) number -9
is formed by inverting all the bits of its opposite number, which is 9
and 00000000000000000000000000001001
in binary, and adding 1
.
In both cases, the sign of the binary number is given by its leftmost bit: for the positive decimal number 9
, the leftmost bit of the binary representation is 0
, and for the negative decimal number -9
, the leftmost bit of the binary representation is 1
.
Given those binary representations of the decimal (base 10) numbers 9
, and -9
:
9 >> 2
yields 2:
9 (base 10): 00000000000000000000000000001001 (base 2) -------------------------------- 9 >> 2 (base 10): 00000000000000000000000000000010 (base 2) = 2 (base 10)
Notice how two rightmost bits, 01
, have been shifted off, and two copies of the leftmost bit, 0
have been shifted in from the left.
-9 >> 2
yields -3
:
-9 (base 10): 11111111111111111111111111110111 (base 2) -------------------------------- -9 >> 2 (base 10): 11111111111111111111111111111101 (base 2) = -3 (base 10)
Notice how two rightmost bits, 11
, have been shifted off. But as far as the leftmost bits: in this case, the leftmost bit is 1
. So two copies of that leftmost 1
bit have been shifted in from the left — which preserves the negative sign.
The binary representation 11111111111111111111111111111101
is equal to the negative decimal (base 10) number -3
, because all negative integers are stored as two's complements, and this one can be calculated by inverting all the bits of the binary representation of the positive decimal (base 10) number 3
, which is 00000000000000000000000000000011
, and then adding one.
If the left operand is a number with more than 32 bits, it will get the most significant bits discarded. For example, the following integer with more than 32 bits will be converted to a 32-bit integer:
Before: 11100110111110100000000000000110000000000001 After: 10100000000000000110000000000001
The right operand will be converted to an unsigned 32-bit integer and then taken modulo 32, so the actual shift offset will always be a positive integer between 0 and 31, inclusive. For example, 100 >> 32
is the same as 100 >> 0
(and produces 100
) because 32 modulo 32 is 0.
Warning:
You may see people using >> 0
to truncate numbers to integers. Right shifting any number x
by 0
returns x
converted to a 32-bit integer, which additionally removes leading bits for numbers outside the range -2147483648 to 2147483647. Use Math.trunc()
instead.
For BigInts, there's no truncation. Conceptually, understand positive BigInts as having an infinite number of leading 0
bits, and negative BigInts having an infinite number of leading 1
bits.
Examples
Using right shift
9 >> 2; // 2
-9 >> 2; // -3
9n >> 2n; // 2n
Specifications
| Specification |
|---|
| ECMAScript® 2026 Language Specification # sec-signed-right-shift-operator |


Remainder assignment (%=)

Remainder assignment (%=)
Baseline
Widely available
This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015.
The remainder assignment (%=
) operator performs remainder on the two operands and assigns the result to the left operand.
Try it
let a = 3;
console.log((a %= 2));
// Expected output: 1
console.log((a %= 0));
// Expected output: NaN
console.log((a %= "hello"));
// Expected output: NaN
Syntax
js
x %= y
Description
x %= y
is equivalent to x = x % y
, except that the expression x
is only evaluated once.
Examples
Using remainder assignment
js
let bar = 5;
bar %= 2; // 1
bar %= "foo"; // NaN
bar %= 0; // NaN
let foo = 3n;
foo %= 2n; // 1n
Specifications
| Specification |
|---|
| ECMAScript® 2026 Language Specification # sec-assignment-operators |


Remainder (%)

Remainder (%)
Baseline
Widely available
This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015.
The remainder (%
) operator returns the remainder left over when one operand is divided by a second operand. It always takes the sign of the dividend.
Try it
console.log(13 % 5);
// Expected output: 3
console.log(-13 % 5);
// Expected output: -3
console.log(4 % 2);
// Expected output: 0
console.log(-4 % 2);
// Expected output: -0
Syntax
x % y
Description
The %
operator is overloaded for two types of operands: number and BigInt. It first coerces both operands to numeric values and tests the types of them. It performs BigInt remainder if both operands become BigInts; otherwise, it performs number remainder. A TypeError
is thrown if one operand becomes a BigInt but the other becomes a number.
For the operation n % d
, n
is called the dividend and d
is called the divisor. The operation returns NaN
if one of the operands is NaN
, n
is ±Infinity, or if d
is ±0. Otherwise, if d
is ±Infinity or if n
is ±0, the dividend n
is returned.
When both operands are non-zero and finite, the remainder r
is calculated as r := n - d * q
where q
is the integer such that r
has the same sign as the dividend n
while being as close to 0 as possible.
Note that while in most languages, '%' is a remainder operator, in some (e.g., Python, Perl) it is a modulo operator. Modulo is defined as k := n - d * q
where q
is the integer such that k
has the same sign as the divisor d
while being as close to 0 as possible. For two values of the same sign, the two are equivalent, but when the operands are of different signs, the modulo result always has the same sign as the divisor, while the remainder has the same sign as the dividend, which can make them differ by one unit of d
. To obtain a modulo in JavaScript, in place of n % d
, use ((n % d) + d) % d
. In JavaScript, the modulo operation (which doesn't have a dedicated operator) is used to normalize the second operand of bitwise shift operators (<<
, >>
, etc.), making the offset always a positive value.
For BigInt division, a RangeError
is thrown if the divisor y
is 0n
. This is because number remainder by zero returns NaN
, but BigInt has no concept of NaN
.
Examples
Remainder with positive dividend
13 % 5; // 3
1 % -2; // 1
1 % 2; // 1
2 % 3; // 2
5.5 % 2; // 1.5
3n % 2n; // 1n
Remainder with negative dividend
-13 % 5; // -3
-1 % 2; // -1
-4 % 2; // -0
-3n % 2n; // -1n
Remainder with NaN
NaN % 2; // NaN
Remainder with Infinity
Infinity % 2; // NaN
Infinity % 0; // NaN
Infinity % Infinity; // NaN
2 % Infinity; // 2
0 % Infinity; // 0
Specifications
| Specification |
|---|
| ECMAScript® 2026 Language Specification # sec-multiplicative-operators |


Property accessors

Property accessors
Baseline
Widely available
This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015.
Property accessors provide access to an object's properties by using the dot notation or the bracket notation.
Try it
const person1 = {};
person1["firstName"] = "Mario";
person1["lastName"] = "Rossi";
console.log(person1.firstName);
// Expected output: "Mario"
const person2 = {
firstName: "John",
lastName: "Doe",
};
console.log(person2["lastName"]);
// Expected output: "Doe"
Syntax
object.propertyName
object[expression]
object.#privateProperty
Description
One can think of an object as an associative array (a.k.a. map, dictionary, hash, lookup table). The keys in this array are the names of the object's properties.
There are two ways to access properties: dot notation and bracket notation.
Dot notation
In the object.propertyName
syntax, the propertyName
must be a valid JavaScript identifier which can also be a reserved word. For example, object.$1
is valid, while object.1
is not.
const variable = object.propertyName;
object.propertyName = value;
const object = {};
object.$1 = "foo";
console.log(object.$1); // 'foo'
const object = {};
object.1 = "bar"; // SyntaxError
console.log(object.1); // SyntaxError
Here, the method named createElement
is retrieved from document
and is called.
document.createElement("pre");
If you use a method for a numeric literal, and the numeric literal has no exponent and no decimal point, you should leave white-space(s) before the dot preceding the method call, so that the dot is not interpreted as a decimal point.
77 .toExponential();
// or
77
.toExponential();
// or
(77).toExponential();
// or
77..toExponential();
// or
77.0.toExponential();
// because 77. === 77.0, no ambiguity
In addition, private elements can only be accessed using dot notation within the class that defines them.
Bracket notation
In the object[expression]
syntax, the expression
should evaluate to a string or Symbol that represents the property's name. So, it can be any string literal, for example, including '1foo'
, '!bar!'
, or even ' '
(a space).
const variable = object[propertyName];
object[propertyName] = value;
This does the exact same thing as the previous example.
document["createElement"]("pre");
A space before bracket notation is allowed.
document ["createElement"]("pre");
Passing expressions that evaluate to property name will do the same thing as directly passing the property name.
const key = "name";
const getKey = () => "name";
const Obj = { name: "Michel" };
Obj["name"]; // returns "Michel"
Obj[key]; // evaluates to Obj["name"], and returns "Michel"
Obj[getKey()]; // evaluates to Obj["name"], and returns "Michel"
However, beware of using square brackets to access properties whose names are given by external input. This may make your code susceptible to object injection attacks.
Property names
Each property name is a string or a Symbol. Any other value, including a number, is coerced to a string. This outputs 'value'
, since 1
is coerced into '1'
.
const object = {};
object["1"] = "value";
console.log(object[1]);
This also outputs 'value'
, since both foo
and bar
are converted to the same string ("[object Object]"
).
const foo = { uniqueProp: 1 };
const bar = { uniqueProp: 2 };
const object = {};
object[foo] = "value";
console.log(object[bar]);
Method binding
It's typical when speaking of an object's properties to make a distinction between properties and methods. However, the property/method distinction is little more than a convention. A method is a property that can be called (for example, if it has a reference to a Function
instance as its value).
A method is not bound to the object that it is a property of. Specifically, this
is not fixed in a method and does not necessarily refer to the object containing the method. Instead, this
is "passed" by the function call. See the reference for this
.
Examples
Bracket notation vs. eval()
JavaScript novices often make the mistake of using eval()
where the bracket notation can be used instead.
For example, the following syntax is often seen in many scripts.
const x = eval(`document.forms.form_name.elements.${strFormControl}.value`);
eval()
is slow and should be avoided whenever possible. Also, strFormControl
would have to hold an identifier, which is not required for names and id
s of form controls. It is better to use bracket notation instead:
const x = document.forms.form_name.elements[strFormControl].value;
Specifications
| Specification |
|---|
| ECMAScript® 2026 Language Specification # sec-property-accessors |


Optional chaining (?.)

Optional chaining (?.)
Baseline
Widely available
This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2020.
The optional chaining (?.
) operator accesses an object's property or calls a function. If the object accessed or function called using this operator is undefined
or null
, the expression short circuits and evaluates to undefined
instead of throwing an error.
Try it
const adventurer = {
name: "Alice",
cat: {
name: "Dinah",
},
};
const dogName = adventurer.dog?.name;
console.log(dogName);
// Expected output: undefined
console.log(adventurer.someNonExistentMethod?.());
// Expected output: undefined
Syntax
obj?.prop
obj?.[expr]
func?.(args)
Description
The ?.
operator is like the .
chaining operator, except that instead of causing an error if a reference is nullish (null
or undefined
), the expression short-circuits with a return value of undefined
. When used with function calls, it returns undefined
if the given function does not exist.
This results in shorter and simpler expressions when accessing chained properties when the possibility exists that a reference may be missing. It can also be helpful while exploring the content of an object when there's no known guarantee as to which properties are required.
For example, consider an object obj
which has a nested structure. Without
optional chaining, looking up a deeply-nested subproperty requires validating the
references in between, such as:
const nestedProp = obj.first && obj.first.second;
The value of obj.first
is confirmed to be non-null
(and
non-undefined
) before accessing the value of
obj.first.second
. This prevents the error that would occur if you accessed
obj.first.second
directly without testing obj.first
.
This is an idiomatic pattern in JavaScript, but it gets verbose when the chain is long, and it's not safe. For example, if obj.first
is a Falsy value that's not null
or undefined
, such as 0
, it would still short-circuit and make nestedProp
become 0
, which may not be desirable.
With the optional chaining operator (?.
), however, you don't have to
explicitly test and short-circuit based on the state of obj.first
before
trying to access obj.first.second
:
const nestedProp = obj.first?.second;
By using the ?.
operator instead of just .
, JavaScript knows
to implicitly check to be sure obj.first
is not null
or
undefined
before attempting to access obj.first.second
. If
obj.first
is null
or undefined
, the expression
automatically short-circuits, returning undefined
.
This is equivalent to the following, except that the temporary variable is in fact not created:
const temp = obj.first;
const nestedProp =
temp === null || temp === undefined ? undefined : temp.second;
Optional chaining cannot be used on a non-declared root object, but can be used with a root object with value undefined
.
undeclaredVar?.prop; // ReferenceError: undeclaredVar is not defined
Optional chaining with function calls
You can use optional chaining when attempting to call a method which may not exist. This can be helpful, for example, when using an API in which a method might be unavailable, either due to the age of the implementation or because of a feature which isn't available on the user's device.
Using optional chaining with function calls causes the expression to automatically
return undefined
instead of throwing an exception if the method isn't
found:
const result = someInterface.customMethod?.();
However, if there is a property with such a name which is not a function, using ?.
will still raise a TypeError
exception "someInterface.customMethod is not a function".
Note:
If someInterface
itself is null
or
undefined
, a TypeError
exception will still be
raised ("someInterface is null"). If you expect that
someInterface
itself may be null
or undefined
,
you have to use ?.
at this position as
well: someInterface?.customMethod?.()
.
eval?.()
is the shortest way to enter indirect eval mode.
Optional chaining with expressions
You can also use the optional chaining operator with bracket notation, which allows passing an expression as the property name:
const propName = "x";
const nestedProp = obj?.[propName];
This is particularly useful for arrays, since array indices must be accessed with square brackets.
function printMagicIndex(arr) {
console.log(arr?.[42]);
}
printMagicIndex([0, 1, 2, 3, 4, 5]); // undefined
printMagicIndex(); // undefined; if not using ?., this would throw an error: "Cannot read properties of undefined (reading '42')"
Invalid optional chaining
It is invalid to try to assign to the result of an optional chaining expression:
const object = {};
object?.property = 1; // SyntaxError: Invalid left-hand side in assignment
Template literal tags cannot be an optional chain (see SyntaxError: tagged template cannot be used with optional chain):
String?.raw`Hello, world!`;
String.raw?.`Hello, world!`; // SyntaxError: Invalid tagged template on optional chain
The constructor of new
expressions cannot be an optional chain (see SyntaxError: new keyword cannot be used with an optional chain):
new Intl?.DateTimeFormat(); // SyntaxError: Invalid optional chain from new expression
new Map?.();
Short-circuiting
When using optional chaining with expressions, if the left operand is null
or undefined
, the expression will not be evaluated. For instance:
const potentiallyNullObj = null;
let x = 0;
const prop = potentiallyNullObj?.[x++];
console.log(x); // 0 as x was not incremented
Subsequent property accesses will not be evaluated either.
const potentiallyNullObj = null;
const prop = potentiallyNullObj?.a.b;
// This does not throw, because evaluation has already stopped at
// the first optional chain
This is equivalent to:
const potentiallyNullObj = null;
const prop =
potentiallyNullObj === null || potentiallyNullObj === undefined
? undefined
: potentiallyNullObj.a.b;
However, this short-circuiting behavior only happens along one continuous "chain" of property accesses. If you group one part of the chain, then subsequent property accesses will still be evaluated.
const potentiallyNullObj = null;
const prop = (potentiallyNullObj?.a).b;
// TypeError: Cannot read properties of undefined (reading 'b')
This is equivalent to:
const potentiallyNullObj = null;
const temp = potentiallyNullObj?.a;
const prop = temp.b;
Except the temp
variable isn't created.
Examples
Basic example
This example looks for the value of the name
property for the member
CSS
in a map when there is no such member. The result is therefore
undefined
.
const myMap = new Map();
myMap.set("JS", { name: "Josh", desc: "I maintain things" });
const nameBar = myMap.get("CSS")?.name;
Dealing with optional callbacks or event handlers
If you use callbacks or fetch methods from an object with a
destructuring pattern, you may have non-existent values that you cannot call as
functions unless you have tested their existence. Using ?.
, you can avoid this extra test:
// Code written without optional chaining
function doSomething(onContent, onError) {
try {
// Do something with the data
} catch (err) {
// Testing if onError really exists
if (onError) {
onError(err.message);
}
}
}
// Using optional chaining with function calls
function doSomething(onContent, onError) {
try {
// Do something with the data
} catch (err) {
onError?.(err.message); // No exception if onError is undefined
}
}
Stacking the optional chaining operator
With nested structures, it is possible to use optional chaining multiple times:
const customer = {
name: "Carl",
details: {
age: 82,
location: "Paradise Falls", // Detailed address is unknown
},
};
const customerCity = customer.details?.address?.city;
// This also works with optional chaining function call
const customerName = customer.name?.getName?.(); // Method does not exist, customerName is undefined
Combining with the nullish coalescing operator
The nullish coalescing operator may be used after optional chaining in order to build a default value when none was found:
function printCustomerCity(customer) {
const customerCity = customer?.city ?? "Unknown city";
console.log(customerCity);
}
printCustomerCity({
name: "Nathan",
city: "Paris",
}); // "Paris"
printCustomerCity({
name: "Carl",
details: { age: 82 },
}); // "Unknown city"
Specifications
| Specification |
|---|
| ECMAScript® 2026 Language Specification # prod-OptionalExpression |


Object initializer

Object initializer
Baseline
Widely available
This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015.
An object initializer is a comma-delimited list of zero or more pairs of property names and associated values of an object, enclosed in curly braces ({}
). Objects can also be initialized using Object.create()
or by invoking a constructor function with the new
operator.
Try it
const object1 = { a: "foo", b: 42, c: {} };
console.log(object1.a);
// Expected output: "foo"
const a = "foo";
const b = 42;
const c = {};
const object2 = { a: a, b: b, c: c };
console.log(object2.b);
// Expected output: 42
const object3 = { a, b, c };
console.log(object3.a);
// Expected output: "foo"
Syntax
o = {
a: "foo",
b: 42,
c: {},
1: "number literal property",
"foo:bar": "string literal property",
shorthandProperty,
method(parameters) {
// …
},
get property() {},
set property(value) {},
[expression]: "computed property",
__proto__: prototype,
...spreadProperty,
};
Description
An object initializer is an expression that describes the initialization of an Object
. Objects consist of properties, which are used to describe an object. The values of object properties can either contain primitive data types or other objects.
Object literal syntax vs. JSON
The object literal syntax is not the same as the JavaScript Object Notation (JSON). Although they look similar, there are differences between them:
- JSON only permits property definition using the
"property": value
syntax. The property name must be double-quoted, and the definition cannot be a shorthand. Computed property names are not allowed either. - JSON object property values can only be strings, numbers,
true
,false
,null
, arrays, or another JSON object. This means JSON cannot express methods or non-plain objects likeMap
orRegExp
. - In JSON,
"__proto__"
is a normal property key. In an object literal, it sets the object's prototype.
JSON is a strict subset of the object literal syntax, meaning that every valid JSON text can be parsed as an object literal, and would likely not cause syntax errors. The only exception is that the object literal syntax prohibits duplicate __proto__
keys, which does not apply to JSON.parse()
. The latter treats __proto__
like a normal property and takes the last occurrence as the property's value. The only time when the object value they represent (a.k.a. their semantic) differ is also when the source contains the __proto__
key — for object literals, it sets the object's prototype; for JSON, it's a normal property.
console.log(JSON.parse('{ "__proto__": 0, "__proto__": 1 }')); // {__proto__: 1}
console.log({ "__proto__": 0, "__proto__": 1 }); // SyntaxError: Duplicate __proto__ fields are not allowed in object literals
console.log(JSON.parse('{ "__proto__": {} }')); // { __proto__: {} }
console.log({ "__proto__": {} }); // {} (with {} as prototype)
Examples
Creating objects
An empty object with no properties can be created like this:
const object = {};
However, the advantage of the literal or initializer notation is, that you are able to quickly create objects with properties inside the curly braces. You notate a list of key: value
pairs delimited by commas.
The following code creates an object with three properties and the keys are "foo"
, "age"
and "baz"
. The values of these keys are a string "bar"
, the number 42
, and another object.
const object = {
foo: "bar",
age: 42,
baz: { myProp: 12 },
};
Accessing properties
Once you have created an object, you might want to read or change them. Object properties can be accessed by using the dot notation or the bracket notation. (See property accessors for detailed information.)
object.foo; // "bar"
object["age"]; // 42
object.baz; // {myProp: 12}
object.baz.myProp; // 12
Property definitions
We have already learned how to notate properties using the initializer syntax. Oftentimes, there are variables in your code that you would like to put into an object. You will see code like this:
const a = "foo";
const b = 42;
const c = {};
const o = {
a: a,
b: b,
c: c,
};
There is a shorter notation available to achieve the same:
const a = "foo";
const b = 42;
const c = {};
// Shorthand property names
const o = { a, b, c };
// In other words,
console.log(o.a === { a }.a); // true
Duplicate property names
When using the same name for your properties, the second property will overwrite the first.
const a = { x: 1, x: 2 };
console.log(a); // {x: 2}
After ES2015, duplicate property names are allowed everywhere, including strict mode. You can also have duplicate property names in classes. The only exception is private elements, which must be unique in the class body.
Method definitions
A property of an object can also refer to a function or a getter or setter method.
const o = {
property: function (parameters) {},
get property() {
return 1;
},
set property(value) {},
};
A shorthand notation is available, so that the keyword function
is no longer necessary.
// Shorthand method names
const o = {
property(parameters) {},
};
There is also a way to concisely define generator methods.
const o = {
*generator() {
// …
},
};
Which is equivalent to this ES5-like notation (but note that ECMAScript 5 has no generators):
const o = {
generator: function* () {
// …
},
};
For more information and examples about methods, see method definitions.
Computed property names
The object initializer syntax also supports computed property names. That allows you to put an expression in square brackets []
, that will be computed and used as the property name. This is reminiscent of the bracket notation of the property accessor syntax, which you may have used to read and set properties already.
Now you can use a similar syntax in object literals, too:
// Computed property names
let i = 0;
const a = {
[`foo${++i}`]: i,
[`foo${++i}`]: i,
[`foo${++i}`]: i,
};
console.log(a.foo1); // 1
console.log(a.foo2); // 2
console.log(a.foo3); // 3
const items = ["A", "B", "C"];
const obj = {
[items]: "Hello",
};
console.log(obj); // A,B,C: "Hello"
console.log(obj["A,B,C"]); // "Hello"
const param = "size";
const config = {
[param]: 12,
[`mobile${param.charAt(0).toUpperCase()}${param.slice(1)}`]: 4,
};
console.log(config); // {size: 12, mobileSize: 4}
Spread properties
Object literals support the spread syntax. It copies own enumerable properties from a provided object onto a new object.
Shallow-cloning (excluding prototype
) or merging objects is now possible using a shorter syntax than Object.assign()
.
const obj1 = { foo: "bar", x: 42 };
const obj2 = { foo: "baz", y: 13 };
const clonedObj = { ...obj1 };
// { foo: "bar", x: 42 }
const mergedObj = { ...obj1, ...obj2 };
// { foo: "baz", x: 42, y: 13 }
Warning:
Note that Object.assign()
triggers setters, whereas the spread syntax doesn't!
Prototype setter
A property definition of the form __proto__: value
or "__proto__": value
does not create a property with the name __proto__
. Instead, if the provided value is an object or null
, it points the [[Prototype]]
of the created object to that value. (If the value is not an object or null
, the object is not changed.)
Note that the __proto__
key is standardized syntax, in contrast to the non-standard and non-performant Object.prototype.__proto__
accessors. It sets the [[Prototype]]
during object creation, similar to Object.create
— instead of mutating the prototype chain.
const obj1 = {};
console.log(Object.getPrototypeOf(obj1) === Object.prototype); // true
const obj2 = { __proto__: null };
console.log(Object.getPrototypeOf(obj2)); // null
const protoObj = {};
const obj3 = { "__proto__": protoObj };
console.log(Object.getPrototypeOf(obj3) === protoObj); // true
const obj4 = { __proto__: "not an object or null" };
console.log(Object.getPrototypeOf(obj4) === Object.prototype); // true
console.log(Object.hasOwn(obj4, "__proto__")); // false
Only a single prototype setter is permitted in an object literal. Multiple prototype setters are a syntax error.
Property definitions that do not use "colon" notation are not prototype setters. They are property definitions that behave identically to similar definitions using any other name.
const __proto__ = "variable";
const obj1 = { __proto__ };
console.log(Object.getPrototypeOf(obj1) === Object.prototype); // true
console.log(Object.hasOwn(obj1, "__proto__")); // true
console.log(obj1.__proto__); // "variable"
const obj2 = { __proto__() { return "hello"; } };
console.log(obj2.__proto__()); // "hello"
const obj3 = { ["__proto__"]: 17 };
console.log(obj3.__proto__); // 17
// Mixing prototype setter with normal own properties with "__proto__" key
const obj4 = { ["__proto__"]: 17, __proto__: {} }; // {__proto__: 17} (with {} as prototype)
const obj5 = {
["__proto__"]: 17,
__proto__: {},
__proto__: null, // SyntaxError: Duplicate __proto__ fields are not allowed in object literals
};
const obj6 = {
["__proto__"]: 17,
["__proto__"]: "hello",
__proto__: null,
}; // {__proto__: "hello"} (with null as prototype)
const obj7 = {
["__proto__"]: 17,
__proto__,
__proto__: null,
}; // {__proto__: "variable"} (with null as prototype)
Specifications
| Specification |
|---|
| ECMAScript® 2026 Language Specification # sec-object-initializer |

const o = {
  *generator() {
    // …
  },
};


Nullish coalescing operator (??)

Nullish coalescing operator (??)
Baseline
Widely available
This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2020.
The nullish coalescing (??
) operator is a logical
operator that returns its right-hand side operand when its left-hand side operand is
null
or undefined
, and otherwise returns its left-hand side
operand.
Try it
const foo = null ?? "default string";
console.log(foo);
// Expected output: "default string"
const baz = 0 ?? 42;
console.log(baz);
// Expected output: 0
Syntax
leftExpr ?? rightExpr
Description
The nullish coalescing operator can be seen as a special case of the logical OR (||
) operator. The latter returns the right-hand side operand if the left operand is any falsy value, not only null
or undefined
. In other words, if you use ||
to provide some default value to another variable foo
, you may encounter unexpected behaviors if you consider some falsy values as usable (e.g., ''
or 0
). See below for more examples.
The nullish coalescing operator has the fifth-lowest operator precedence, directly lower than ||
and directly higher than the conditional (ternary) operator.
It is not possible to combine either the AND (&&
) or OR operators (||
) directly with ??
. A syntax error will be thrown in such cases.
null || undefined ?? "foo"; // raises a SyntaxError
true && undefined ?? "foo"; // raises a SyntaxError
Instead, provide parenthesis to explicitly indicate precedence:
(null || undefined) ?? "foo"; // returns "foo"
Examples
Using the nullish coalescing operator
In this example, we will provide default values but keep values other than null
or undefined
.
const nullValue = null;
const emptyText = ""; // falsy
const someNumber = 42;
const valA = nullValue ?? "default for A";
const valB = emptyText ?? "default for B";
const valC = someNumber ?? 0;
console.log(valA); // "default for A"
console.log(valB); // "" (as the empty string is not null or undefined)
console.log(valC); // 42
Assigning a default value to a variable
Earlier, when one wanted to assign a default value to a variable, a common pattern was to use the logical OR operator (||
):
let foo;
// foo is never assigned any value so it is still undefined
const someDummyText = foo || "Hello!";
However, due to ||
being a boolean logical operator, the left-hand-side operand was coerced to a boolean for the evaluation and any falsy value (including 0
, ''
, NaN
, false
, etc.) was not returned. This behavior may cause unexpected consequences if you consider 0
, ''
, or NaN
as valid values.
const count = 0;
const text = "";
const qty = count || 42;
const message = text || "hi!";
console.log(qty); // 42 and not 0
console.log(message); // "hi!" and not ""
The nullish coalescing operator avoids this pitfall by only returning the second operand when the first one evaluates to either null
or undefined
(but no other falsy values):
const myText = ""; // An empty string (which is also a falsy value)
const notFalsyText = myText || "Hello world";
console.log(notFalsyText); // Hello world
const preservingFalsy = myText ?? "Hi neighborhood";
console.log(preservingFalsy); // '' (as myText is neither undefined nor null)
Short-circuiting
Like the 'OR' and 'AND' logical operators, the right-hand side expression is not evaluated if the left-hand side proves to be neither null
nor undefined
.
function a() {
console.log("a was called");
return undefined;
}
function b() {
console.log("b was called");
return false;
}
function c() {
console.log("c was called");
return "foo";
}
console.log(a() ?? c());
// Logs "a was called" then "c was called" and then "foo"
// as a() returned undefined so both expressions are evaluated
console.log(b() ?? c());
// Logs "b was called" then "false"
// as b() returned false (and not null or undefined), the right
// hand side expression was not evaluated
Relationship with the optional chaining operator (?.)
The nullish coalescing operator treats undefined
and null
as specific values. So does the optional chaining operator (?.
), which is useful to access a property of an object which may be null
or undefined
. Combining them, you can safely access a property of an object which may be nullish and provide a default value if it is.
const foo = { someFooProp: "hi" };
console.log(foo.someFooProp?.toUpperCase() ?? "not available"); // "HI"
console.log(foo.someBarProp?.toUpperCase() ?? "not available"); // "not available"
Specifications
| Specification |
|---|
| ECMAScript® 2026 Language Specification # prod-CoalesceExpression |


Nullish coalescing assignment (??=)

Nullish coalescing assignment (??=)
Baseline
Widely available
This feature is well established and works across many devices and browser versions. It’s been available across browsers since September 2020.
The nullish coalescing assignment (??=
) operator, also known as the logical nullish assignment operator, only evaluates the right operand and assigns to the left if the left operand is nullish (null
or undefined
).
Try it
const a = { duration: 50 };
a.speed ??= 25;
console.log(a.speed);
// Expected output: 25
a.duration ??= 10;
console.log(a.duration);
// Expected output: 50
Syntax
x ??= y
Description
Nullish coalescing assignment short-circuits, meaning that x ??= y
is equivalent to x ?? (x = y)
, except that the expression x
is only evaluated once.
No assignment is performed if the left-hand side is not nullish, due to short-circuiting of the nullish coalescing operator. For example, the following does not throw an error, despite x
being const
:
const x = 1;
x ??= 2;
Neither would the following trigger the setter:
const x = {
get value() {
return 1;
},
set value(v) {
console.log("Setter called");
},
};
x.value ??= 2;
In fact, if x
is not nullish, y
is not evaluated at all.
const x = 1;
x ??= console.log("y evaluated");
// Logs nothing
Examples
Using nullish coalescing assignment
You can use the nullish coalescing assignment operator to apply default values to object properties. Compared to using destructuring and default values, ??=
also applies the default value if the property has value null
.
function config(options) {
options.duration ??= 100;
options.speed ??= 25;
return options;
}
config({ duration: 125 }); // { duration: 125, speed: 25 }
config({}); // { duration: 100, speed: 25 }
Specifications
| Specification |
|---|
| ECMAScript® 2026 Language Specification # sec-assignment-operators |


Strict mode

Strict mode
Note: Sometimes you'll see the default, non-strict mode referred to as sloppy mode. This isn't an official term, but be aware of it, just in case.
JavaScript's strict mode is a way to opt in to a restricted variant of JavaScript, thereby implicitly opting-out of "sloppy mode". Strict mode isn't just a subset: it intentionally has different semantics from normal code. Strict mode code and non-strict mode code can coexist, so scripts can opt into strict mode incrementally.
Strict mode makes several changes to normal JavaScript semantics:
- Eliminates some JavaScript silent errors by changing them to throw errors.
- Fixes mistakes that make it difficult for JavaScript engines to perform optimizations: strict mode code can sometimes be made to run faster than identical code that's not strict mode.
- Prohibits some syntax likely to be defined in future versions of ECMAScript.
Invoking strict mode
Strict mode applies to entire scripts or to individual functions. It doesn't apply to block statements enclosed in {}
braces; attempting to apply it to such contexts does nothing. eval
code, Function
code, event handler attributes, strings passed to setTimeout()
, and related functions are either function bodies or entire scripts, and invoking strict mode in them works as expected.
Strict mode for scripts
To invoke strict mode for an entire script, put the exact statement "use strict";
(or 'use strict';
) before any other statements.
// Whole-script strict mode syntax
"use strict";
const v = "Hi! I'm a strict mode script!";
Strict mode for functions
Likewise, to invoke strict mode for a function, put the exact statement "use strict";
(or 'use strict';
) in the function's body before any other statements.
function myStrictFunction() {
// Function-level strict mode syntax
"use strict";
function nested() {
return "And so am I!";
}
return `Hi! I'm a strict mode function! ${nested()}`;
}
function myNotStrictFunction() {
return "I'm not strict.";
}
The "use strict"
directive can only be applied to the body of functions with simple parameters. Using "use strict"
in functions with rest, default, or destructured parameters is a syntax error.
function sum(a = 1, b = 2) {
// SyntaxError: "use strict" not allowed in function with default parameter
"use strict";
return a + b;
}
Strict mode for modules
The entire contents of JavaScript modules are automatically in strict mode, with no statement needed to initiate it.
function myStrictFunction() {
// because this is a module, I'm strict by default
}
export default myStrictFunction;
Strict mode for classes
All parts of a class's body are strict mode code, including both class declarations and class expressions.
class C1 {
// All code here is evaluated in strict mode
test() {
delete Object.prototype;
}
}
new C1().test(); // TypeError, because test() is in strict mode
const C2 = class {
// All code here is evaluated in strict mode
};
// Code here may not be in strict mode
delete Object.prototype; // Will not throw error
Changes in strict mode
Strict mode changes both syntax and runtime behavior. Changes generally fall into these categories:
- changes converting mistakes into errors (as syntax errors or at runtime)
- changes simplifying how variable references are resolved
- changes simplifying
eval
andarguments
- changes making it easier to write "secure" JavaScript
- changes anticipating future ECMAScript evolution.
Converting mistakes into errors
Strict mode changes some previously-accepted mistakes into errors. JavaScript was designed to be easy for novice developers, and sometimes it gives operations which should be errors non-error semantics. Sometimes this fixes the immediate problem, but sometimes this creates worse problems in the future. Strict mode treats these mistakes as errors so that they're discovered and promptly fixed.
Assigning to undeclared variables
Strict mode makes it impossible to accidentally create global variables. In sloppy mode, mistyping a variable in an assignment creates a new property on the global object and continues to "work". Assignments which would accidentally create global variables throw an error in strict mode:
"use strict";
let mistypeVariable;
// Assuming no global variable mistypeVarible exists
// this line throws a ReferenceError due to the
// misspelling of "mistypeVariable" (lack of an "a")
mistypeVarible = 17;
Failing to assign to object properties
Strict mode makes assignments which would otherwise silently fail to throw an exception. There are three ways to fail a property assignment:
- assignment to a non-writable data property
- assignment to a getter-only accessor property
- assignment to a new property on a non-extensible object
For example, NaN
is a non-writable global variable. In sloppy mode, assigning to NaN
does nothing; the developer receives no failure feedback. In strict mode, assigning to NaN
throws an exception.
"use strict";
// Assignment to a non-writable global
undefined = 5; // TypeError
Infinity = 5; // TypeError
// Assignment to a non-writable property
const obj1 = {};
Object.defineProperty(obj1, "x", { value: 42, writable: false });
obj1.x = 9; // TypeError
// Assignment to a getter-only property
const obj2 = {
get x() {
return 17;
},
};
obj2.x = 5; // TypeError
// Assignment to a new property on a non-extensible object
const fixed = {};
Object.preventExtensions(fixed);
fixed.newProp = "ohai"; // TypeError
Failing to delete object properties
Attempts to delete a non-configurable or otherwise undeletable (e.g., it's intercepted by a proxy's deleteProperty
handler which returns false
) property throw in strict mode (where before the attempt would have no effect):
"use strict";
delete Object.prototype; // TypeError
delete [].length; // TypeError
Strict mode also forbids deleting plain names. delete name
in strict mode is a syntax error:
"use strict";
var x;
delete x; // syntax error
If the name is a configurable global property, prefix it with globalThis
to delete it.
"use strict";
delete globalThis.x;
Duplicate parameter names
Strict mode requires that function parameter names be unique. In sloppy mode, the last duplicated argument hides previous identically-named arguments. Those previous arguments remain available through arguments
, so they're not completely inaccessible. Still, this hiding makes little sense and is probably undesirable (it might hide a typo, for example), so in strict mode, duplicate argument names are a syntax error:
function sum(a, a, c) {
// syntax error
"use strict";
return a + a + c; // wrong if this code ran
}
It is also a syntax error in non-strict mode to have duplicate parameter names, if the function has a default parameter, rest parameter, or destructured parameter.
Legacy octal literals
Strict mode forbids a 0
-prefixed octal literal. In sloppy mode, a number beginning with a 0
, such as 0644
, is interpreted as an octal number (0644 === 420
), if all digits are smaller than 8. Novice developers sometimes believe a leading-zero prefix has no semantic meaning, so they might use it as an alignment device — but this changes the number's meaning! A leading-zero syntax for the octal is rarely useful and can be mistakenly used, so strict mode makes it a syntax error:
"use strict";
const sum =
015 + // syntax error
197 +
142;
The standardized way to denote octal literals is via the 0o
prefix. For example:
const sumWithOctal = 0o10 + 8;
console.log(sumWithOctal); // 16
Octal escape sequences, such as "\45"
, which is equal to "%"
, can be used to represent characters by extended-ASCII character code numbers in octal. In strict mode, this is a syntax error. More formally, it's disallowed to have \
followed by any decimal digit other than 0
, or \0
followed by a decimal digit; for example \9
and \07
.
Setting properties on primitive values
Strict mode forbids setting properties on primitive values. Accessing a property on a primitive implicitly creates a wrapper object that's unobservable, so in sloppy mode, setting properties is ignored (no-op). In strict mode, a TypeError
is thrown.
"use strict";
false.true = ""; // TypeError
(14).sailing = "home"; // TypeError
"with".you = "far away"; // TypeError
Duplicate property names
Duplicate property names used to be considered a SyntaxError
in strict mode. With the introduction of computed property names, making duplication possible at runtime, this restriction was removed in ES2015.
"use strict";
const o = { p: 1, p: 2 }; // syntax error prior to ECMAScript 2015
Note: Making code that used to error become non-errors is always considered backwards-compatible. This is a good part of the language being strict about throwing errors: it leaves room for future semantic changes.
Simplifying scope management
Strict mode simplifies how variable names map to particular variable definitions in the code. Many compiler optimizations rely on the ability to say that variable X is stored in that location: this is critical to fully optimizing JavaScript code. JavaScript sometimes makes this basic mapping of name to variable definition in the code impossible to perform until runtime. Strict mode removes most cases where this happens, so the compiler can better optimize strict mode code.
Removal of the with statement
Strict mode prohibits with
. The problem with with
is that any name inside the block might map either to a property of the object passed to it, or to a variable in surrounding (or even global) scope, at runtime; it's impossible to know which beforehand. Strict mode makes with
a syntax error, so there's no chance for a name in a with
to refer to an unknown location at runtime:
"use strict";
const x = 17;
with (obj) {
// Syntax error
// If this weren't strict mode, would this be const x, or
// would it instead be obj.x? It's impossible in general
// to say without running the code, so the name can't be
// optimized.
x;
}
The alternative of assigning the object to a short name variable, then accessing the corresponding property on that variable, stands ready to replace with
.
Non-leaking eval
In strict mode, eval
does not introduce new variables into the surrounding scope. In sloppy mode, eval("var x;")
introduces a variable x
into the surrounding function or the global scope. This means that, in general, in a function containing a call to eval
, every name not referring to an argument or local variable must be mapped to a particular definition at runtime (because that eval
might have introduced a new variable that would hide the outer variable). In strict mode, eval
creates variables only for the code being evaluated, so eval
can't affect whether a name refers to an outer variable or some local variable:
var x = 17;
var evalX = eval("'use strict'; var x = 42; x;");
console.assert(x === 17);
console.assert(evalX === 42);
Whether the string passed to eval()
is evaluated in strict mode depends on how eval()
is invoked (direct eval or indirect eval).
Block-scoped function declarations
The JavaScript language specification, since its start, had not allowed function declarations nested in block statements. However, it was so intuitive that most browsers implemented it as an extension grammar. Unfortunately, the implementations' semantics diverged, and it became impossible for the language specification to reconcile all implementations. Therefore, block-scoped function declarations are only explicitly specified in strict mode (whereas they were once disallowed in strict mode), while sloppy mode behavior remains divergent among browsers.
Making eval and arguments simpler
Strict mode makes arguments
and eval
less bizarrely magical. Both involve a considerable amount of magical behavior in sloppy mode: eval
to add or remove bindings and to change binding values, and arguments
syncing named arguments with its indexed properties. Strict mode makes great strides toward treating eval
and arguments
as keywords.
Preventing binding or assigning eval and arguments
The names eval
and arguments
can't be bound or assigned in language syntax. All these attempts to do so are syntax errors:
"use strict";
eval = 17;
arguments++;
++eval;
const obj = { set p(arguments) {} };
let eval;
try {
} catch (arguments) {}
function x(eval) {}
function arguments() {}
const y = function eval() {};
const f = new Function("arguments", "'use strict'; return 17;");
No syncing between parameters and arguments indices
Strict mode code doesn't sync indices of the arguments
object with each parameter binding. In a sloppy mode function whose first argument is arg
, setting arg
also sets arguments[0]
, and vice versa (unless no arguments were provided or arguments[0]
is deleted). arguments
objects for strict mode functions store the original arguments when the function was invoked. arguments[i]
does not track the value of the corresponding named argument, nor does a named argument track the value in the corresponding arguments[i]
.
function f(a) {
"use strict";
a = 42;
return [a, arguments[0]];
}
const pair = f(17);
console.assert(pair[0] === 42);
console.assert(pair[1] === 17);
"Securing" JavaScript
Strict mode makes it easier to write "secure" JavaScript. Some websites now provide ways for users to write JavaScript which will be run by the website on behalf of other users. JavaScript in browsers can access the user's private information, so such JavaScript must be partially transformed before it is run, to censor access to forbidden functionality. JavaScript's flexibility makes it effectively impossible to do this without many runtime checks. Certain language functions are so pervasive that performing runtime checks has a considerable performance cost. A few strict mode tweaks, plus requiring that user-submitted JavaScript be strict mode code and that it be invoked in a certain manner, substantially reduce the need for those runtime checks.
No this substitution
The value passed as this
to a function in strict mode is not forced into being an object (a.k.a. "boxed"). For a sloppy mode function, this
is always an object: either the provided object, if called with an object-valued this
; or the boxed value of this
, if called with a primitive as this
; or the global object, if called with undefined
or null
as this
. (Use call
, apply
, or bind
to specify a particular this
.) Not only is automatic boxing a performance cost, but exposing the global object in browsers is a security hazard because the global object provides access to functionality that "secure" JavaScript environments must restrict. Thus for a strict mode function, the specified this
is not boxed into an object, and if unspecified, this
is undefined
instead of globalThis
:
"use strict";
function fun() {
return this;
}
console.assert(fun() === undefined);
console.assert(fun.call(2) === 2);
console.assert(fun.apply(null) === null);
console.assert(fun.call(undefined) === undefined);
console.assert(fun.bind(true)() === true);
Removal of stack-walking properties
In strict mode it's no longer possible to "walk" the JavaScript stack. Many implementations used to implement some extension features that make it possible to detect the upstream caller of a function. When a function fun
is in the middle of being called, fun.caller
is the function that most recently called fun
, and fun.arguments
is the arguments
for that invocation of fun
. Both extensions are problematic for "secure" JavaScript because they allow "secured" code to access "privileged" functions and their (potentially unsecured) arguments. If fun
is in strict mode, both fun.caller
and fun.arguments
are non-deletable properties which throw when set or retrieved:
function restricted() {
"use strict";
restricted.caller; // throws a TypeError
restricted.arguments; // throws a TypeError
}
function privilegedInvoker() {
return restricted();
}
privilegedInvoker();
Similarly, arguments.callee
is no longer supported. In sloppy mode, arguments.callee
refers to the enclosing function. This use case is weak: name the enclosing function! Moreover, arguments.callee
substantially hinders optimizations like inlining functions, because it must be made possible to provide a reference to the un-inlined function if arguments.callee
is accessed. arguments.callee
for strict mode functions is a non-deletable property which throws an error when set or retrieved:
"use strict";
function f() {
return arguments.callee;
}
f(); // throws a TypeError
Future-proofing JavaScript
Extra reserved words
Reserved words are identifiers that can't be used as variable names. Strict mode reserves some more names than sloppy mode, some of which are already used in the language, and some of which are reserved for the future to make future syntax extensions easier to implement.
Transitioning to strict mode
Strict mode has been designed so that the transition to it can be made gradually. It is possible to change each file individually and even to transition code to strict mode down to the function granularity.
You can migrate a codebase to strict mode by first adding "use strict"
to a piece of source code, and then fixing all execution errors, while watching out for semantic differences.
Syntax errors
When adding 'use strict';
, the following cases will throw a SyntaxError
before the script is executing:
- Octal syntax
const n = 023;
with
statement- Using
delete
on a variable namedelete myVariable
; - Using
eval
orarguments
as variable or function argument name - Using one of the newly reserved keywords (in prevision for future language features):
implements
,interface
,let
,package
,private
,protected
,public
,static
, andyield
- Declaring two function parameters with the same name
function f(a, b, b) {}
- Declaring the same property name twice in an object literal
{a: 1, b: 3, a: 7}
. This constraint was later removed (bug 1041128).
These errors are good, because they reveal plain errors or bad practices. They occur before the code is running, so they are easily discoverable as long as the code gets parsed by the runtime.
New runtime errors
JavaScript used to silently fail in contexts where what was done should be an error. Strict mode throws in such cases. If your code base contains such cases, testing will be necessary to be sure nothing is broken. You can screen for such errors at the function granularity level.
- Assigning to an undeclared variable throws a
ReferenceError
. This used to set a property on the global object, which is rarely the expected effect. If you really want to set a value to the global object, explicitly assign it as a property onglobalThis
. - Failing to assign to an object's property (e.g., it's read-only) throws a
TypeError
. In sloppy mode, this would silently fail. - Deleting a non-deletable property throws a
TypeError
. In sloppy mode, this would silently fail. - Accessing
arguments.callee
,strictFunction.caller
, orstrictFunction.arguments
throws aTypeError
if the function is in strict mode. If you are usingarguments.callee
to call the function recursively, you can use a named function expression instead.
Semantic differences
These differences are very subtle differences. It's possible that a test suite doesn't catch this kind of subtle difference. Careful review of your code base will probably be necessary to be sure these differences don't affect the semantics of your code. Fortunately, this careful review can be done gradually down the function granularity.
this
-
In sloppy mode, function calls like
f()
would pass the global object as thethis
value. In strict mode, it is nowundefined
. When a function was called withcall
orapply
, if the value was a primitive value, this one was boxed into an object (or the global object forundefined
andnull
). In strict mode, the value is passed directly without conversion or replacement. arguments
-
In sloppy mode, modifying a value in the
arguments
object modifies the corresponding named argument. This made optimizations complicated for JavaScript engine and made code harder to read/understand. In strict mode, thearguments
object is created and initialized with the same values than the named arguments, but changes to either thearguments
object or the named arguments aren't reflected in one another. eval
-
In strict mode code,
eval
doesn't create a new variable in the scope from which it was called. Also, of course, in strict mode, the string is evaluated with strict mode rules. Thorough testing will need to be performed to make sure nothing breaks. Not using eval if you don't really need it may be another pragmatic solution. - Block-scoped function declarations
-
In sloppy mode, a function declaration inside a block may be visible outside the block and even callable. In strict mode, a function declaration inside a block is only visible inside the block.
Specifications
| Specification |
|---|
| ECMAScript® 2026 Language Specification |


# Contributors by commit history
https://github.com/mdn/content/commits/main/files/en-us/web/javascript/reference/operators/nullish_coalescing_assignment/index.md

# Original Wiki contributors
hinell
jridgewell
fscholz


# Contributors by commit history
https://github.com/mdn/content/commits/main/files/en-us/web/javascript/reference/operators/nullish_coalescing/index.md

# Original Wiki contributors
OtterOne
sideshowbarker
BlackBindy
fscholz
snek
Dorward
neverRare
koesper
Zearin_Galaurum
Mik13
wbamberg
SphinxKnight


# Contributors by commit history
https://github.com/mdn/content/commits/main/files/en-us/web/javascript/reference/operators/object_initializer/index.md

# Original Wiki contributors
sharq
Muhammad_Ali
Anonymous
fscholz
wbamberg
jinbeomhong
Zearin_Galaurum
muhammadghazali
sideshowbarker
MadaraUchiha
lmcarreiro
JonathanPool
torazaburo
jameshkramer
martinczerwi
bergus
zbjornson
nmve
chrisjimallen
kdex
roryokane
rwaldron
stevemasta34
kayellpeee
bouzlibop
hovosanoyan
stevemao
Olson.dev
Llbe
jpmedley
Robg1
Philip Chee
Waldo
tschneidereit


Iteration protocols

Iteration protocols
Iteration protocols aren't new built-ins or syntax, but protocols. These protocols can be implemented by any object by following some conventions.
There are two protocols: The iterable protocol and the iterator protocol.
The iterable protocol
The iterable protocol allows JavaScript objects to define or customize their iteration behavior, such as what values are looped over in a for...of
construct. Some built-in types are built-in iterables with a default iteration behavior, such as Array
or Map
, while other types (such as Object
) are not.
In order to be iterable, an object must implement the [Symbol.iterator]()
method, meaning that the object (or one of the objects up its prototype chain) must have a property with a [Symbol.iterator]
key which is available via constant Symbol.iterator
:
[Symbol.iterator]()
-
A zero-argument function that returns an object, conforming to the iterator protocol.
Whenever an object needs to be iterated (such as at the beginning of a for...of
loop), its [Symbol.iterator]()
method is called with no arguments, and the returned iterator is used to obtain the values to be iterated.
Note that when this zero-argument function is called, it is invoked as a method on the iterable object. Therefore inside of the function, the this
keyword can be used to access the properties of the iterable object, to decide what to provide during the iteration.
This function can be an ordinary function, or it can be a generator function, so that when invoked, an iterator object is returned. Inside of this generator function, each entry can be provided by using yield
.
The iterator protocol
The iterator protocol defines a standard way to produce a sequence of values (either finite or infinite), and potentially a return value when all values have been generated.
An object is an iterator when it implements a next()
method with the following semantics:
next()
-
A function that accepts zero or one argument and returns an object conforming to the
IteratorResult
interface (see below). If a non-object value gets returned (such asfalse
orundefined
) when a built-in language feature (such asfor...of
) is using the iterator, aTypeError
("iterator.next() returned a non-object value"
) will be thrown.
All iterator protocol methods (next()
, return()
, and throw()
) are expected to return an object implementing the IteratorResult
interface. It must have the following properties:
done
Optional-
A boolean that's
false
if the iterator was able to produce the next value in the sequence. (This is equivalent to not specifying thedone
property altogether.)Has the value
true
if the iterator has completed its sequence. In this case,value
optionally specifies the return value of the iterator. value
Optional-
Any JavaScript value returned by the iterator. Can be omitted when
done
istrue
.
In practice, neither property is strictly required; if an object without either property is returned, it's effectively equivalent to { done: false, value: undefined }
.
If an iterator returns a result with done: true
, any subsequent calls to next()
are expected to return done: true
as well, although this is not enforced on the language level.
The next
method can receive a value which will be made available to the method body. No built-in language feature will pass any value. The value passed to the next
method of generators will become the value of the corresponding yield
expression.
Optionally, the iterator can also implement the return(value)
and throw(exception)
methods, which, when called, tells the iterator that the caller is done with iterating it and can perform any necessary cleanup (such as closing database connection).
return(value)
Optional-
A function that accepts zero or one argument and returns an object conforming to the
IteratorResult
interface, typically withvalue
equal to thevalue
passed in anddone
equal totrue
. Calling this method tells the iterator that the caller does not intend to make any morenext()
calls and can perform any cleanup actions. When built-in language features callreturn()
for cleanup,value
is alwaysundefined
. throw(exception)
Optional-
A function that accepts zero or one argument and returns an object conforming to the
IteratorResult
interface, typically withdone
equal totrue
. Calling this method tells the iterator that the caller detects an error condition, andexception
is typically anError
instance. No built-in language feature callsthrow()
for cleanup purposes — it's a special feature of generators for the symmetry ofreturn
/throw
.
Note:
It is not possible to know reflectively (i.e., without actually calling next()
and validating the returned result) whether a particular object implements the iterator protocol.
It is very easy to make an iterator also iterable: just implement a [Symbol.iterator]()
method that returns this
.
// Satisfies both the Iterator Protocol and Iterable
const myIterator = {
next() {
// …
},
[Symbol.iterator]() {
return this;
},
};
Such object is called an iterable iterator. Doing so allows an iterator to be consumed by the various syntaxes expecting iterables — therefore, it is seldom useful to implement the Iterator Protocol without also implementing Iterable. (In fact, almost all syntaxes and APIs expect iterables, not iterators.) The generator object is an example:
const generatorObject = (function* () {
yield 1;
yield 2;
yield 3;
})();
console.log(typeof generatorObject.next);
// "function" — it has a next method (which returns the right result), so it's an iterator
console.log(typeof generatorObject[Symbol.iterator]);
// "function" — it has a [Symbol.iterator] method (which returns the right iterator), so it's an iterable
console.log(generatorObject[Symbol.iterator]() === generatorObject);
// true — its [Symbol.iterator] method returns itself (an iterator), so it's an iterable iterator
All built-in iterators inherit from Iterator.prototype
, which implements the [Symbol.iterator]()
method as returning this
, so that built-in iterators are also iterable.
However, when possible, it's better for iterable[Symbol.iterator]()
to return different iterators that always start from the beginning, like Set.prototype[Symbol.iterator]()
does.
The async iterator and async iterable protocols
There are another pair of protocols used for async iteration, named async iterator and async iterable protocols. They have very similar interfaces compared to the iterable and iterator protocols, except that each return value from the calls to the iterator methods is wrapped in a promise.
An object implements the async iterable protocol when it implements the following methods:
[Symbol.asyncIterator]()
-
A zero-argument function that returns an object, conforming to the async iterator protocol.
An object implements the async iterator protocol when it implements the following methods:
next()
-
A function that accepts zero or one argument and returns a promise. The promise fulfills to an object conforming to the
IteratorResult
interface, and the properties have the same semantics as those of the sync iterator's. return(value)
Optional-
A function that accepts zero or one argument and returns a promise. The promise fulfills to an object conforming to the
IteratorResult
interface, and the properties have the same semantics as those of the sync iterator's. throw(exception)
Optional-
A function that accepts zero or one argument and returns a promise. The promise fulfills to an object conforming to the
IteratorResult
interface, and the properties have the same semantics as those of the sync iterator's.
Interactions between the language and iteration protocols
The language specifies APIs that either produce or consume iterables and iterators.
Built-in iterables
String
, Array
, TypedArray
, Map
, Set
, and Segments
(returned by Intl.Segmenter.prototype.segment()
) are all built-in iterables, because each of their prototype
objects implements a [Symbol.iterator]()
method. In addition, the arguments
object and some DOM collection types such as NodeList
are also iterables.
There is no object in the core JavaScript language that is async iterable. Some web APIs, such as ReadableStream
, have the Symbol.asyncIterator
method set by default.
Generator functions return generator objects, which are iterable iterators. Async generator functions return async generator objects, which are async iterable iterators.
The iterators returned from built-in iterables actually all inherit from a common class Iterator
, which implements the aforementioned [Symbol.iterator]() { return this; }
method, making them all iterable iterators. The Iterator
class also provides additional helper methods in addition to the next()
method required by the iterator protocol. You can inspect an iterator's prototype chain by logging it in a graphical console.
console.log([][Symbol.iterator]()); Array Iterator {} [[Prototype]]: Array Iterator ==> This is the prototype shared by all array iterators next: ƒ next() Symbol(Symbol.toStringTag): "Array Iterator" [[Prototype]]: Object ==> This is the prototype shared by all built-in iterators Symbol(Symbol.iterator): ƒ [Symbol.iterator]() [[Prototype]]: Object ==> This is Object.prototype
Built-in APIs accepting iterables
There are many APIs that accept iterables. Some examples include:
Map()
WeakMap()
Set()
WeakSet()
Promise.all()
Promise.allSettled()
Promise.race()
Promise.any()
Array.from()
Object.groupBy()
Map.groupBy()
const myObj = {};
new WeakSet(
(function* () {
yield {};
yield myObj;
yield {};
})(),
).has(myObj); // true
Syntaxes expecting iterables
Some statements and expressions expect iterables, for example the for...of
loops, array and parameter spreading, yield*
, and array destructuring:
for (const value of ["a", "b", "c"]) {
console.log(value);
}
// "a"
// "b"
// "c"
console.log([..."abc"]); // ["a", "b", "c"]
function* gen() {
yield* ["a", "b", "c"];
}
console.log(gen().next()); // { value: "a", done: false }
[a, b, c] = new Set(["a", "b", "c"]);
console.log(a); // "a"
When built-in syntaxes are iterating an iterator, and the last result's done
is false
(i.e., the iterator is able to produce more values) but no more values are needed, the return
method will get called if present. This can happen, for example, if a break
or return
is encountered in a for...of
loop, or if all identifiers are already bound in an array destructuring.
const obj = {
[Symbol.iterator]() {
let i = 0;
return {
next() {
i++;
console.log("Returning", i);
if (i === 3) return { done: true, value: i };
return { done: false, value: i };
},
return() {
console.log("Closing");
return { done: true };
},
};
},
};
const [a] = obj;
// Returning 1
// Closing
const [b, c, d] = obj;
// Returning 1
// Returning 2
// Returning 3
// Already reached the end (the last call returned `done: true`),
// so `return` is not called
console.log([b, c, d]); // [1, 2, undefined]; the value associated with `done: true` is not reachable
for (const b of obj) {
break;
}
// Returning 1
// Closing
The for await...of
loop and yield*
in async generator functions (but not sync generator functions) are the only ways to interact with async iterables. Using for...of
, array spreading, etc. on an async iterable that's not also a sync iterable (i.e., it has [Symbol.asyncIterator]()
but no [Symbol.iterator]()
) will throw a TypeError: x is not iterable.
Error handling
Because iteration involves transferring control back and forth between the iterator and the consumer, error handling happens in both ways: how the consumer handles errors thrown by the iterator, and how the iterator handles errors thrown by the consumer. When you are using one of the built-in ways of iteration, the language may also throw errors because the iterable breaks certain invariants. We will describe how built-in syntaxes generate and handle errors, which can be used as a guideline for your own code if you are manually stepping the iterator.
Non-well-formed iterables
Errors may happen when acquiring the iterator from the iterable. The language invariant enforced here is that the iterable must produce a valid iterator:
- It has a callable
[Symbol.iterator]()
method. - The
[Symbol.iterator]()
method returns an object. - The object returned by
[Symbol.iterator]()
has a callablenext()
method.
When using built-in syntax to initiate iteration on a non-well-formed iterable, a TypeError is thrown.
const nonWellFormedIterable = { [Symbol.iterator]: 1 };
[...nonWellFormedIterable]; // TypeError: nonWellFormedIterable is not iterable
nonWellFormedIterable[Symbol.iterator] = () => 1;
[...nonWellFormedIterable]; // TypeError: [Symbol.iterator]() returned a non-object value
nonWellFormedIterable[Symbol.iterator] = () => ({});
[...nonWellFormedIterable]; // TypeError: nonWellFormedIterable[Symbol.iterator]().next is not a function
For async iterables, if its [Symbol.asyncIterator]()
property has value undefined
or null
, JavaScript falls back to using the [Symbol.iterator]
property instead (and wraps the resulting iterator into an async iterator by forwarding the methods). Otherwise, the [Symbol.asyncIterator]
property must conform to the above invariants too.
This type of errors can be prevented by first validating the iterable before attempting to iterate it. However, it's fairly rare because usually you know the type of the object you are iterating over. If you are receiving this iterable from some other code, you should just let the error propagate to the caller so they know an invalid input was provided.
Errors during iteration
Most errors happen when stepping the iterator (calling next()
). The language invariant enforced here is that the next()
method must return an object (for async iterators, an object after awaiting). Otherwise, a TypeError is thrown.
If the invariant is broken or the next()
method throws an error (for async iterators, it may also return a rejected promise), the error is propagated to the caller. For built-in syntaxes, the iteration in progress is aborted without retrying or cleanup (with the assumption that if the next()
method threw the error, then it has cleaned up already). If you are manually calling next()
, you may catch the error and retry calling next()
, but in general you should assume the iterator is already closed.
If the caller decides to exit iteration for any reason other than the errors in the previous paragraph, such as when it enters an error state in its own code (for example, while handling an invalid value produced by the iterator), it should call the return()
method on the iterator, if one exists. This allows the iterator to perform any cleanup. The return()
method is only called for premature exits—if next()
returns done: true
, the return()
method is not called, with the assumption that the iterator has already cleaned up.
The return()
method might be invalid too! The language also enforces that the return()
method must return an object and throws a TypeError otherwise. If the return()
method throws an error, the error is propagated to the caller. However, if the return()
method is called because the caller encountered an error in its own code, then this error overrides the error thrown by the return()
method.
Usually, the caller implements error handling like this:
try {
for (const value of iterable) {
// …
}
} catch (e) {
// Handle the error
}
The catch
will be able to catch errors thrown when iterable
is not a valid iterable, when next()
throws an error, when return()
throws an error (if the for
loop exits early), and when the for
loop body throws an error.
Most iterators are implemented with generator functions, so we will demonstrate how generator functions typically handle errors:
function* gen() {
try {
yield doSomething();
yield doSomethingElse();
} finally {
cleanup();
}
}
The lack of catch
here causes errors thrown by doSomething()
or doSomethingElse()
to propagate to the caller of gen
. If these errors are caught within the generator function (which is equally advisable), the generator function can decide to continue yielding values or to exit early. However, the finally
block is necessary for generators that keep open resources. The finally
block is guaranteed to run, either when the last next()
is called or when return()
is called.
Forwarding errors
Some built-in syntaxes wrap an iterator into another iterator. They include the iterator produced by Iterator.from()
, iterator helper methods (map()
, filter()
, take()
, drop()
, and flatMap()
), yield*
, and a hidden wrapper when you use async iteration (for await...of
, Array.fromAsync
) on sync iterators. The wrapped iterator is then responsible for forwarding errors between the inner iterator and the caller.
- All wrapper iterators directly forward the
next()
method of the inner iterator, including its return value and thrown errors. - Wrapper iterators generally directly forward the
return()
method of the inner iterator. If thereturn()
method doesn't exist on the inner iterator, it returns{ done: true, value: undefined }
instead. In the case of iterator helpers: if the iterator helper'snext()
method has not been called, after trying to callreturn()
on the inner iterator, the current iterator always returns{ done: true, value: undefined }
. This is consistent with generator functions where execution hasn't entered theyield*
expression yet. yield*
is the only built-in syntax that forwards thethrow()
method of the inner iterator. For information on howyield*
forwards thereturn()
andthrow()
methods, see its own reference.
Examples
User-defined iterables
You can make your own iterables like this:
const myIterable = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
},
};
console.log([...myIterable]); // [1, 2, 3]
Basic iterator
Iterators are stateful by nature. If you don't define it as a generator function (as the example above shows), you would likely want to encapsulate the state in a closure.
function makeIterator(array) {
let nextIndex = 0;
return {
next() {
return nextIndex < array.length
? {
value: array[nextIndex++],
done: false,
}
: {
done: true,
};
},
};
}
const it = makeIterator(["yo", "ya"]);
console.log(it.next().value); // 'yo'
console.log(it.next().value); // 'ya'
console.log(it.next().done); // true
Infinite iterator
function idMaker() {
let index = 0;
return {
next() {
return {
value: index++,
done: false,
};
},
};
}
const it = idMaker();
console.log(it.next().value); // 0
console.log(it.next().value); // 1
console.log(it.next().value); // 2
// …
Defining an iterable with a generator
function* makeGenerator(array) {
let nextIndex = 0;
while (nextIndex < array.length) {
yield array[nextIndex++];
}
}
const gen = makeGenerator(["yo", "ya"]);
console.log(gen.next().value); // 'yo'
console.log(gen.next().value); // 'ya'
console.log(gen.next().done); // true
function* idMaker() {
let index = 0;
while (true) {
yield index++;
}
}
const it = idMaker();
console.log(it.next().value); // 0
console.log(it.next().value); // 1
console.log(it.next().value); // 2
// …
Defining an iterable with a class
State encapsulation can be done with private fields as well.
class SimpleClass {
#data;
constructor(data) {
this.#data = data;
}
[Symbol.iterator]() {
// Use a new index for each iterator. This makes multiple
// iterations over the iterable safe for non-trivial cases,
// such as use of break or nested looping over the same iterable.
let index = 0;
return {
// Note: using an arrow function allows `this` to point to the
// one of `[Symbol.iterator]()` instead of `next()`
next: () => {
if (index >= this.#data.length) {
return { done: true };
}
return { value: this.#data[index++], done: false };
},
};
}
}
const simple = new SimpleClass([1, 2, 3, 4, 5]);
for (const val of simple) {
console.log(val); // 1 2 3 4 5
}
Overriding built-in iterables
For example, a String
is a built-in iterable object:
const someString = "hi";
console.log(typeof someString[Symbol.iterator]); // "function"
String
's default iterator returns the string's code points one by one:
const iterator = someString[Symbol.iterator]();
console.log(`${iterator}`); // "[object String Iterator]"
console.log(iterator.next()); // { value: "h", done: false }
console.log(iterator.next()); // { value: "i", done: false }
console.log(iterator.next()); // { value: undefined, done: true }
You can redefine the iteration behavior by supplying our own [Symbol.iterator]()
:
// need to construct a String object explicitly to avoid auto-boxing
const someString = new String("hi");
someString[Symbol.iterator] = function () {
return {
// this is the iterator object, returning a single element (the string "bye")
next() {
return this._first
? { value: "bye", done: (this._first = false) }
: { done: true };
},
_first: true,
};
};
Notice how redefining [Symbol.iterator]()
affects the behavior of built-in constructs that use the iteration protocol:
console.log([...someString]); // ["bye"]
console.log(`${someString}`); // "hi"
Concurrent modifications when iterating
Almost all iterables have the same underlying semantic: they don't copy the data at the time when iteration starts. Rather, they keep a pointer and move it around. Therefore, if you add, delete, or modify elements in the collection while iterating over the collection, you may inadvertently change whether other unchanged elements in the collection are visited. This is very similar to how iterative array methods work.
Consider the following case using a URLSearchParams
:
const searchParams = new URLSearchParams(
"deleteme1=value1&key2=value2&key3=value3",
);
// Delete unwanted keys
for (const [key, value] of searchParams) {
console.log(key);
if (key.startsWith("deleteme")) {
searchParams.delete(key);
}
}
// Output:
// deleteme1
// key3
Note how it never logs key2
. This is because a URLSearchParams
is underlyingly a list of key-value pairs. When deleteme1
is visited and deleted, all other entries are shifted to the left by one, so key2
occupies the position that deleteme1
used to be in, and when the pointer moves to the next key, it lands on key3
.
Certain iterable implementations avoid this problem by setting "tombstone" values to avoid shifting the remaining values. Consider the similar code using a Map
:
const myMap = new Map([
["deleteme1", "value1"],
["key2", "value2"],
["key3", "value3"],
]);
for (const [key, value] of myMap) {
console.log(key);
if (key.startsWith("deleteme")) {
myMap.delete(key);
}
}
// Output:
// deleteme1
// key2
// key3
Note how it logs all keys. This is because Map
doesn't shift the remaining keys when one is deleted. If you want to implement something similar, here's how it may look:
const tombstone = Symbol("tombstone");
class MyIterable {
#data;
constructor(data) {
this.#data = data;
}
delete(deletedKey) {
for (let i = 0; i < this.#data.length; i++) {
if (this.#data[i][0] === deletedKey) {
this.#data[i] = tombstone;
return true;
}
}
return false;
}
*[Symbol.iterator]() {
for (const data of this.#data) {
if (data !== tombstone) {
yield data;
}
}
}
}
const myIterable = new MyIterable([
["deleteme1", "value1"],
["key2", "value2"],
["key3", "value3"],
]);
for (const [key, value] of myIterable) {
console.log(key);
if (key.startsWith("deleteme")) {
myIterable.delete(key);
}
}
Warning: Concurrent modifications, in general, are very bug-prone and confusing. Unless you know precisely how the iterable is implemented, it's best to avoid modifying the collection while iterating over it.
Specifications
| Specification |
|---|
| ECMAScript® 2026 Language Specification # sec-iteration |