JavaScript Cheat Sheet
A complete JavaScript syntax and commands reference for beginners and intermediate developers. Quickly find variables, arrays, objects, functions, DOM methods, events, promises, async/await and more.
Complete JavaScript Cheat Sheet
JavaScript is the programming language that powers modern websites and web applications. Whether you’re creating interactive user interfaces, handling events, working with APIs, or building full-stack applications, JavaScript is an essential skill for every web developer.
This free JavaScript Cheat Sheet is designed as a fast, practical reference rather than a traditional tutorial. Instead of reading through lengthy explanations, you can quickly find the syntax, methods, and code examples you need while writing or debugging JavaScript.
The guide covers everything from variables, data types, operators, strings, arrays, objects, and functions to conditionals, loops, DOM manipulation, events, asynchronous JavaScript, JSON, modules, and error handling. Every section includes concise explanations, practical code examples, and quick-reference tables to help you learn faster and work more efficiently.
Whether you’re a beginner learning JavaScript, a student preparing for an exam, or an experienced developer looking for a quick reference, this cheat sheet provides a structured overview of the language in one place.
JavaScript Quick Reference
Quickly find commonly used JavaScript syntax, methods and language features. Use the cards below to jump directly to the relevant reference section.
Basic Syntax
These are the JavaScript syntax elements used most often when writing and debugging code.
| Syntax | Description | Example | Copy |
|---|---|---|---|
console.log() |
Print a value to the browser console. | console.log("Hello!"); |
|
// |
Create a single-line comment. | // This is a comment |
|
/* */ |
Create a multi-line comment. | /* Comment */ |
|
; |
End a statement. Often optional, but commonly used. | const name = "Alice"; |
|
{ } |
Define a block of code. | if (active) { } |
|
typeof |
Return the type of a value. | typeof "Hello" |
|
prompt() |
Display an input dialog in the browser. | prompt("Your name?") |
|
alert() |
Display a browser alert dialog. | alert("Welcome!"); |
F12 or
Ctrl + Shift + I to view messages printed with
console.log().
JavaScript Variables (var, let & const)
Variables store data that can be reused throughout your program. Modern JavaScript primarily uses
let and const, while var is mostly kept for maintaining older code.
Understanding the differences is essential for writing reliable JavaScript.
Variable Declaration Comparison
The table below compares the three ways to declare variables in JavaScript.
| Keyword | Scope | Can Reassign | Can Redeclare | Hoisted | Recommended |
|---|---|---|---|---|---|
var |
Function | ✅ Yes | ✅ Yes | ✅ Yes | ❌ No |
let |
Block | ✅ Yes | ❌ No | ✅ Yes* | ✅ Yes |
const |
Block | ❌ No | ❌ No | ✅ Yes* | ⭐⭐ Best Choice |
let and const are technically hoisted, they remain inaccessible until execution reaches their declaration. This behavior is known as the Temporal Dead Zone (TDZ).
Using let
Use let when a variable needs to change later.
let age = 25;
age = 26;
console.log(age);
26
Using const
Use const for values that should never be reassigned.
const pi = 3.14159;
console.log(pi);
3.14159
Using var
The var keyword was the original way to declare variables in JavaScript. It is still supported but should generally be avoided in modern applications because of its function scope and redeclaration behavior.
var city = "London";
var city = "Paris";
console.log(city);
Paris
let and const, var allows variables to be redeclared, which can easily introduce bugs.
Block Scope
Variables declared with let and const only exist inside the block where they are created.
if (true) {
let message = "Hello";
console.log(message);
}
// console.log(message);
Hello
Function Scope
Variables declared with var are limited to the function in which they are declared.
function demo() {
var score = 100;
console.log(score);
}
demo();
100
Hoisting
JavaScript moves declarations to the top of their scope before execution. This behavior is called hoisting.
| Keyword | Hoisted | Accessible Before Declaration |
|---|---|---|
var |
✅ Yes | Returns undefined |
let |
✅ Yes | ❌ No (Temporal Dead Zone) |
const |
✅ Yes | ❌ No (Temporal Dead Zone) |
Temporal Dead Zone (TDZ)
The Temporal Dead Zone is the period between entering a scope and declaring a let or const variable. Accessing the variable before its declaration throws an error.
console.log(age);
let age = 20;
ReferenceError
const with Objects and Arrays
A common misconception is that const makes objects and arrays immutable. In reality, it only prevents reassignment of the variable itself.
const user = {
name: "Alice"
};
user.name = "Bob";
console.log(user.name);
Bob
const prevents reassignment of the variable, not modification of the object’s properties.
Naming Conventions
Use descriptive names that clearly explain the purpose of the variable.
| ❌ Poor | ✅ Better | Reason |
|---|---|---|
| x | userAge | Clearly describes the value. |
| d | currentDate | More meaningful. |
| arr | shoppingCart | Explains the contents. |
| n | productName | Easy to understand. |
| temp | averageTemperature | Specific variable name. |
Best Practices
| Practice | Recommendation |
|---|---|
| Default choice | Use const. |
| Changing values | Use let. |
| Legacy code | Avoid var. |
| Variable names | Use descriptive camelCase names. |
| Scope | Keep variables inside the smallest possible scope. |
| Constants | Store fixed values using const. |
Common Mistakes
| Mistake | Why it’s a Problem | Better Solution |
|---|---|---|
Using var |
Unexpected scope behavior. | Use let or const. |
Using let everywhere |
Variables become mutable unnecessarily. | Prefer const. |
| Short variable names | Harder to understand. | Choose descriptive names. |
| Global variables | Increase the risk of bugs. | Limit scope whenever possible. |
| Ignoring TDZ | Causes ReferenceError. | Declare variables before use. |
Quick Summary
| Keyword | Use When |
|---|---|
const |
The value should not be reassigned. |
let |
The value will change later. |
var |
Working with older JavaScript code. |
const generally preferred over let in modern JavaScript?
Using const makes code more predictable because variables cannot be accidentally reassigned. Developers only use let when a value genuinely needs to change.
JavaScript Data Types
JavaScript supports several built-in data types that represent different kinds of values. Understanding the difference between primitive and reference types is essential for writing reliable JavaScript applications.
JavaScript Data Types Overview
JavaScript has seven primitive data types and one reference type (Object), which includes arrays, functions and other complex structures.
| Data Type | Description | Example | Mutable |
|---|---|---|---|
String |
Represents textual data. | "Hello" |
❌ No |
Number |
Represents integers and floating-point numbers. | 42, 3.14 |
❌ No |
Boolean |
Represents true or false values. | true |
❌ No |
Undefined |
A variable declared but not assigned a value. | undefined |
❌ No |
Null |
Represents the intentional absence of a value. | null |
❌ No |
BigInt |
Stores very large integers. | 123456789n |
❌ No |
Symbol |
Represents unique identifiers. | Symbol() |
❌ No |
Object |
Stores collections of properties and complex data. | {} |
✅ Yes |
Primitive Data Types
Primitive values are immutable and stored directly in memory. Assigning a primitive value copies the actual value.
| Type | Example | typeof Result |
|---|---|---|
| String | "JavaScript" |
"string" |
| Number | 100 |
"number" |
| Boolean | false |
"boolean" |
| Undefined | undefined |
"undefined" |
| BigInt | 99n |
"bigint" |
| Symbol | Symbol() |
"symbol" |
| Null | null |
"object" ⚠️ |
typeof null returns "object", this is a long-standing JavaScript bug kept for backward compatibility.
Using typeof
The typeof operator returns the type of a value. It is commonly used for debugging and validation.
console.log(typeof "Hello");
console.log(typeof 42);
console.log(typeof true);
console.log(typeof undefined);
string
number
boolean
undefined
Reference Types
Reference types store a reference to a value in memory rather than storing the value directly. Objects, arrays and functions are all reference types in JavaScript.
| Type | Description | Example | typeof |
|---|---|---|---|
| Object | Stores key-value pairs. | { name: "Alice" } |
"object" |
| Array | Stores an ordered collection. | [1, 2, 3] |
"object" |
| Function | Stores reusable behavior. | function greet() {} |
"function" |
| Date | Represents a date and time. | new Date() |
"object" |
| RegExp | Represents a regular expression. | /hello/i |
"object" |
Objects
Objects store related data as properties. Each property has a key and a value.
const user = {
name: "Alice",
age: 30,
active: true
};
console.log(user.name);
Alice
Arrays
Arrays are special objects used to store ordered collections of values.
const colors = ["red", "green", "blue"];
console.log(colors[0]);
console.log(colors.length);
red
3
Functions are Objects
Functions are callable objects. They can be assigned to variables, passed as arguments and returned from other functions.
function greet() {
return "Hello";
}
const sayHello = greet;
console.log(sayHello());
Hello
Copy by Value
Primitive values are copied by value. Changing one variable does not affect the other.
let first = 10;
let second = first;
second = 20;
console.log(first);
console.log(second);
10
20
Copy by Reference
Objects are assigned by reference. Two variables can point to the same object in memory.
const firstUser = {
name: "Alice"
};
const secondUser = firstUser;
secondUser.name = "Bob";
console.log(firstUser.name);
console.log(secondUser.name);
Bob
Bob
Primitive vs Reference Types
| Feature | Primitive | Reference |
|---|---|---|
| Examples | String, Number, Boolean | Object, Array, Function |
| Stored As | Actual value | Reference to a value |
| Mutable | No | Yes |
| Assignment | Copies the value | Copies the reference |
| Equality | Compares values | Compares references |
Array.isArray(value) to check whether a value is an array.
The typeof operator returns "object" for arrays.
Type Conversion
JavaScript can convert values automatically or explicitly. Explicit conversion is usually clearer and safer.
| Function | Purpose | Example |
|---|---|---|
String() |
Convert a value to a string. | String(123) |
Number() |
Convert a value to a number. | Number("42") |
Boolean() |
Convert a value to true or false. | Boolean(1) |
parseInt() |
Convert text to an integer. | parseInt("42px", 10) |
parseFloat() |
Convert text to a decimal number. | parseFloat("3.14") |
const ageText = "30";
const ageNumber = Number(ageText);
console.log(ageNumber);
console.log(typeof ageNumber);
30
number
Implicit Type Coercion
JavaScript sometimes converts values automatically. This is called type coercion.
console.log("5" + 2);
console.log("5" - 2);
console.log(true + 1);
52
3
2
+ operator concatenates when one value is a string, while operators such as -, * and / usually convert strings to numbers.
Truthy and Falsy Values
Every JavaScript value is treated as either truthy or falsy in conditions.
| Falsy Values | Examples of Truthy Values |
|---|---|
false |
true |
0 |
1, -1 |
-0 |
"0" |
0n |
"false" |
"" |
[] |
null |
{} |
undefined |
function() {} |
NaN |
Most other values |
const username = "";
if (username) {
console.log("Username exists");
} else {
console.log("Username is missing");
}
Username is missing
Strict Equality vs Loose Equality
Use strict equality whenever possible. It compares both value and type without automatic conversion.
| Operator | Meaning | Example | Result |
|---|---|---|---|
== |
Loose equality | "5" == 5 |
true |
=== |
Strict equality | "5" === 5 |
false |
!= |
Loose inequality | "5" != 5 |
false |
!== |
Strict inequality | "5" !== 5 |
true |
=== and !== because their behavior is more predictable.
NaN
NaN means “Not a Number”. It usually appears when a numeric conversion or calculation fails.
const result = Number("hello");
console.log(result);
console.log(Number.isNaN(result));
NaN
true
NaN === NaN returns false. Use Number.isNaN() to test for it.
Checking Common Types
| Check | Recommended Syntax |
|---|---|
| String | typeof value === "string" |
| Number | typeof value === "number" |
| Boolean | typeof value === "boolean" |
| Array | Array.isArray(value) |
| Null | value === null |
| NaN | Number.isNaN(value) |
| Date | value instanceof Date |
Best Practices
| Practice | Recommendation |
|---|---|
| Equality | Prefer === and !==. |
| Conversion | Use explicit conversion when possible. |
| Arrays | Use Array.isArray(). |
| Missing values | Distinguish between null and undefined. |
| Numbers | Validate conversions with Number.isNaN(). |
| Objects | Remember that assignment copies the reference. |
Quick Summary
| Concept | Remember |
|---|---|
| Primitive values | Copied by value. |
| Objects and arrays | Assigned by reference. |
typeof null |
Returns "object". |
| Array checking | Use Array.isArray(). |
| Strict equality | Use ===. |
| Failed number conversion | Returns NaN. |
Primitive values are copied by value, so each variable has its own independent value. Objects, arrays and functions are reference types, so multiple variables can point to the same value in memory.
JavaScript Operators
JavaScript operators are special symbols used to perform calculations, compare values, assign data, combine conditions, and manipulate variables. Operators are a fundamental part of nearly every JavaScript expression.
Understanding operator behavior is especially important in JavaScript because automatic type conversion, operator precedence, and differences between strict and loose comparison can produce unexpected results.
JavaScript operators can be grouped into arithmetic, assignment, comparison, logical, string, conditional, nullish, optional chaining, bitwise, type, and special operators.
| Operator category | Purpose | Common operators |
|---|---|---|
| Arithmetic | Perform mathematical calculations | +, -, *, /, %, ** |
| Assignment | Assign or update values | =, +=, -=, ??= |
| Comparison | Compare values and return a Boolean | ===, !==, >, < |
| Logical | Combine or invert conditions | &&, ||, ! |
| String | Join strings together | +, += |
| Conditional | Select a value based on a condition | condition ? value1 : value2 |
| Type and special | Inspect types, properties, or object relationships | typeof, instanceof, in, delete |
JavaScript Arithmetic Operators
Arithmetic operators perform mathematical operations on numeric values. They can be used with numbers, variables, function results, and more complex expressions.
| Operator | Name | Example | Result |
|---|---|---|---|
+ |
Addition | 10 + 5 |
15 |
- |
Subtraction | 10 - 5 |
5 |
* |
Multiplication | 10 * 5 |
50 |
/ |
Division | 10 / 5 |
2 |
% |
Remainder | 10 % 3 |
1 |
** |
Exponentiation | 2 ** 4 |
16 |
++ |
Increment | value++ |
Adds 1 |
-- |
Decrement | value-- |
Subtracts 1 |
+ |
Unary plus | +"42" |
42 |
- |
Unary negation | -10 |
-10 |
Basic Arithmetic Examples
const price = 120;
const quantity = 3;
const discount = 20;
const subtotal = price * quantity;
const total = subtotal - discount;
const averagePrice = total / quantity;
console.log(subtotal);
console.log(total);
console.log(averagePrice);
360
340
113.33333333333333
Arithmetic expressions can combine several operators. JavaScript evaluates the expression according to operator precedence rather than simply reading every operation from left to right.
The Remainder Operator
The remainder operator % returns the remainder left after one
number is divided by another. It is commonly used to detect even and odd
numbers, create repeating patterns, and cycle through array positions.
const number = 17;
const isEven = number % 2 === 0;
const isOdd = number % 2 !== 0;
console.log(isEven);
console.log(isOdd);
console.log(17 % 5);
false
true
2
Use index % array.length when you need an index to wrap back
to the beginning of an array.
const colors = ["red", "green", "blue"];
for (let index = 0; index < 7; index++) {
const color = colors[index % colors.length];
console.log(color);
}
red
green
blue
red
green
blue
red
The Exponentiation Operator
The exponentiation operator ** raises the left operand to the
power of the right operand. It is the modern alternative to
Math.pow().
const square = 5 ** 2;
const cube = 4 ** 3;
const squareRoot = 81 ** 0.5;
console.log(square);
console.log(cube);
console.log(squareRoot);
25
64
9
Prefer base ** exponent when it improves readability. Use
Math.pow(base, exponent) mainly when maintaining older code
or supporting environments where exponentiation syntax is unavailable.
Increment and Decrement Operators
The increment operator ++ adds one to a variable, while the
decrement operator -- subtracts one. Both operators have prefix
and postfix forms.
| Syntax | Behavior | Returned value |
|---|---|---|
value++ |
Increment after reading the current value | Original value |
++value |
Increment before reading the value | Updated value |
value-- |
Decrement after reading the current value | Original value |
--value |
Decrement before reading the value | Updated value |
let firstValue = 5;
let secondValue = 5;
const postfixResult = firstValue++;
const prefixResult = ++secondValue;
console.log(postfixResult);
console.log(firstValue);
console.log(prefixResult);
console.log(secondValue);
5
6
6
6
Prefix and postfix operators can make complex expressions difficult to understand. Avoid using several increments inside the same expression. Separate the operations into clear statements instead.
// Less readable
const result = counter++ + ++counter;
// Clearer
counter += 1;
const firstValue = counter;
counter += 1;
const secondValue = counter;
const result = firstValue + secondValue;
Unary Plus and Unary Negation
Unary operators work with a single operand. Unary plus attempts to convert its operand into a number, while unary negation converts the value to a number and reverses its sign.
const numericString = "42";
const decimalString = "19.95";
const invalidNumber = "JavaScript";
console.log(+numericString);
console.log(+decimalString);
console.log(+invalidNumber);
console.log(-numericString);
42
19.95
NaN
-42
Unary plus performs implicit numeric conversion. For production code,
Number(), Number.parseInt(), or
Number.parseFloat() may communicate your intent more clearly.
Addition Versus String Concatenation
The + operator has two different purposes. It adds numbers, but
it also concatenates strings. When one operand is a string, JavaScript may
convert the other operand into a string.
console.log(10 + 5);
console.log("10" + 5);
console.log(10 + "5");
console.log(10 + 5 + "px");
console.log("Total: " + 10 + 5);
15
105
105
15px
Total: 105
Values received from form fields are normally strings. Convert them to
numbers before performing arithmetic, or the + operator may
concatenate the values instead of adding them.
const firstInput = "25";
const secondInput = "15";
// Incorrect for numeric addition
const concatenatedResult = firstInput + secondInput;
// Correct
const numericResult = Number(firstInput) + Number(secondInput);
console.log(concatenatedResult);
console.log(numericResult);
2515
40
Division by Zero and Special Numeric Results
JavaScript does not throw a normal runtime error when a number is divided
by zero. Instead, it returns special numeric values such as
Infinity, -Infinity, or NaN.
console.log(10 / 0);
console.log(-10 / 0);
console.log(0 / 0);
console.log("hello" * 5);
Infinity
-Infinity
NaN
NaN
Use Number.isFinite() when a calculation must produce a
normal finite number. Use Number.isNaN() when checking
specifically for NaN.
function divideNumbers(dividend, divisor) {
const result = dividend / divisor;
if (!Number.isFinite(result)) {
return "The calculation did not produce a finite number.";
}
return result;
}
console.log(divideNumbers(20, 4));
console.log(divideNumbers(20, 0));
5
The calculation did not produce a finite number.
Floating-Point Precision
JavaScript uses IEEE 754 floating-point numbers. Some decimal values cannot be represented exactly in binary, which can create small precision errors.
const result = 0.1 + 0.2;
console.log(result);
console.log(result === 0.3);
0.30000000000000004
false
For simple currency calculations, store amounts in the smallest unit, such as cents, rather than using decimal values directly.
const firstPriceInCents = 10;
const secondPriceInCents = 20;
const totalInCents = firstPriceInCents + secondPriceInCents;
const totalInDollars = totalInCents / 100;
console.log(totalInDollars);
0.3
- Use
+,-,*, and/for standard calculations. - Use
%for remainders, even/odd checks, and repeating indexes. - Use
**for exponentiation. - Understand the difference between prefix and postfix increment.
- Convert string input before using the addition operator.
- Validate calculations that may produce
InfinityorNaN. - Be aware of floating-point precision when working with decimal values.
What is the difference between value++ and
++value?
Both increment the variable by one. The postfix form
value++ returns the original value before incrementing,
while the prefix form ++value returns the updated value.
Assignment Operators
Assignment operators assign values to variables and are commonly used
to update existing values. Besides the standard assignment operator
(=), JavaScript provides shorthand operators that make
code shorter and easier to read.
| Operator | Name | Equivalent To |
|---|---|---|
= |
Assignment | x = y |
+= |
Add and assign | x = x + y |
-= |
Subtract and assign | x = x - y |
*= |
Multiply and assign | x = x * y |
/= |
Divide and assign | x = x / y |
%= |
Remainder and assign | x = x % y |
**= |
Exponentiate and assign | x = x ** y |
Basic Assignment
The assignment operator (=) stores a value inside a
variable. The value can be a literal, an expression, or the result of
a function.
let score = 100;
score = 250;
console.log(score);
250
Compound Assignment Operators
Compound assignment operators combine an arithmetic operation and an assignment into a single statement. They improve readability and reduce repetitive code.
let total = 20;
total += 5;
total *= 2;
total -= 10;
total /= 3;
console.log(total);
13.333333333333334
Use compound assignment operators whenever you are updating an existing variable. They are concise, widely recognized, and make your intent immediately clear.
Compound assignment does not change JavaScript’s type coercion
rules. For example, using += with strings performs
string concatenation instead of numeric addition.
let value = "10";
value += 5;
console.log(value);
105
- Use
=to assign a value. - Use
+=,-=,*=,/=for cleaner updates. - Compound operators improve readability.
- Remember that
+=concatenates strings.
Logical Assignment Operators
Logical assignment operators were introduced in modern JavaScript to
simplify conditional assignments. They combine logical operators with
assignment, making your code cleaner and reducing unnecessary
if statements.
| Operator | Description | Assigns When… |
|---|---|---|
||= |
Logical OR assignment | The current value is falsy. |
&&= |
Logical AND assignment | The current value is truthy. |
??= |
Nullish assignment | The current value is null or undefined. |
OR Assignment (||=)
The ||= operator assigns a value only if the current value
is falsy (false, 0,
"", null, undefined or
NaN).
let username = "";
username ||= "Guest";
console.log(username);
Guest
AND Assignment (&&=)
The &&= operator assigns a new value only when the current
value is truthy.
let isLoggedIn = true;
isLoggedIn &&= false;
console.log(isLoggedIn);
false
Nullish Assignment (??=)
The ??= operator assigns a value only when the variable is
null or undefined. Unlike
||=, values such as 0 or an empty string are
preserved.
let theme = null;
let age = 0;
theme ??= "Light";
age ??= 18;
console.log(theme);
console.log(age);
Light
0
Prefer ??= when assigning default values to user input
or configuration settings. It avoids accidentally replacing valid
values like 0, false, or an empty string.
Do not confuse ||= with ??=. If
0 or "" are valid values, use
??= to avoid overwriting them.
||=assigns when the value is falsy.&&=assigns when the value is truthy.??=assigns only when the value isnullorundefined.??=is usually the safest choice for default values.
Comparison Operators
Comparison operators compare two values and always return a Boolean
(true or false). They are essential for
decision-making in JavaScript and are commonly used with
if statements, loops, filtering, and conditional
expressions.
| Operator | Name | Example | Returns |
|---|---|---|---|
== |
Loose equality | 5 == "5" |
true |
=== |
Strict equality | 5 === "5" |
false |
!= |
Loose inequality | 5 != "5" |
false |
!== |
Strict inequality | 5 !== "5" |
true |
> |
Greater than | 8 > 5 |
true |
< |
Less than | 3 < 7 |
true |
>= |
Greater than or equal | 5 >= 5 |
true |
<= |
Less than or equal | 5 <= 10 |
true |
Using Comparison Operators
Comparison operators are frequently used to control the flow of a
program. They determine whether conditions are met and are commonly
combined with if, else, loops, and logical
operators.
const age = 20;
if (age >= 18) {
console.log("Access granted");
} else {
console.log("Access denied");
}
Access granted
Comparison Operators Return Booleans
Every comparison produces either true or
false. These Boolean values can be stored in variables,
returned from functions, or used directly in conditional statements.
const score = 92;
const passed = score >= 50;
const perfect = score === 100;
console.log(passed);
console.log(perfect);
true
false
Write comparisons so they read naturally. Clear conditions improve readability and make your code easier to maintain.
- Comparison operators always return
trueorfalse. - They are used extensively with
if, loops, and logical operators. - Choose the operator that clearly expresses your intent.
Strict vs Loose Equality
JavaScript provides two sets of equality operators:
loose equality (==) and
strict equality (===).
Understanding the difference is essential because loose equality performs
automatic type conversion, while strict equality compares both
value and data type.
| Operator | Compares Value | Compares Type | Recommended |
|---|---|---|---|
== |
✔ Yes | ✘ No | Rarely |
=== |
✔ Yes | ✔ Yes | ✔ Always |
!= |
✔ Yes | ✘ No | Rarely |
!== |
✔ Yes | ✔ Yes | ✔ Always |
Loose Equality (==)
The loose equality operator attempts to convert values to the same data type before comparing them. This behavior is known as type coercion.
console.log(5 == "5");
console.log(false == 0);
console.log(null == undefined);
true
true
true
Automatic type conversion can make code unpredictable and harder to debug, especially in large applications.
Strict Equality (===)
Strict equality compares both the value and the data type. No automatic type conversion occurs.
console.log(5 === "5");
console.log(false === 0);
console.log(null === undefined);
console.log(5 === 5);
false
false
false
true
Real-World Example
User input from forms is usually returned as a string. Using strict equality helps prevent unexpected matches caused by automatic type conversion.
const userAge = "18";
if (Number(userAge) === 18) {
console.log("Adult");
}
Adult
Most JavaScript style guides, including Airbnb, Google, and MDN,
recommend using === and !== by default.
Only use == when you intentionally want type coercion
and fully understand its behavior.
==compares values after type conversion.===compares both value and data type.!==is generally preferred over!=.- Modern JavaScript code should almost always use
===.
Why do professional JavaScript developers almost always prefer
=== over ==?
Because strict equality avoids implicit type coercion, making code more predictable, easier to debug, and less prone to subtle bugs.
Relational Operators
Relational operators compare numeric, string, or date values to determine
their relative order. They always return a Boolean value
(true or false) and are commonly used in
conditional statements and loops.
| Operator | Description | Example | Result |
|---|---|---|---|
> |
Greater than | 10 > 5 |
true |
< |
Less than | 5 < 10 |
true |
>= |
Greater than or equal | 10 >= 10 |
true |
<= |
Less than or equal | 8 <= 10 |
true |
Comparing Numbers
Relational operators are most commonly used to compare numeric values. They make it easy to validate ranges, enforce limits, and control application logic.
const temperature = 24;
console.log(temperature > 20);
console.log(temperature < 0);
console.log(temperature >= 24);
console.log(temperature <= 30);
true
false
true
true
Using Comparisons in Conditions
Relational operators are frequently combined with
if statements to control the execution of code.
const score = 78;
if (score >= 50) {
console.log("You passed the exam.");
} else {
console.log("You failed the exam.");
}
You passed the exam.
Use relational operators together with meaningful variable names.
Expressions like score >= passingScore are easier to
understand than comparing against unexplained numbers.
>checks if the left value is greater.<checks if the left value is smaller.>=includes equality.<=includes equality.- Relational operators always return a Boolean.
Logical Operators
Logical operators are used to combine, evaluate, or invert conditions.
They are commonly used with comparison operators to build more advanced
decision-making logic in if statements, loops, and
conditional expressions.
| Operator | Name | Description |
|---|---|---|
&& |
Logical AND | Returns true only if both conditions are true. |
|| |
Logical OR | Returns true if at least one condition is true. |
! |
Logical NOT | Reverses a Boolean value. |
Logical AND (&&)
The logical AND operator returns true only when every
condition evaluates to true. If any condition is false,
the entire expression becomes false.
const age = 25;
const hasTicket = true;
const canEnter = age >= 18 && hasTicket;
console.log(canEnter);
true
Logical OR (||)
The logical OR operator returns true when at least one
condition is true. It returns false only when every
condition is false.
const isAdmin = false;
const isModerator = true;
const hasAccess = isAdmin || isModerator;
console.log(hasAccess);
true
Logical NOT (!)
The logical NOT operator reverses a Boolean value. It converts
true to false and false to
true.
const isLoggedIn = false;
console.log(!isLoggedIn);
true
Keep logical expressions simple and readable. If a condition becomes
too long, consider storing parts of it in descriptive variables such
as hasPermission or isEligible.
&&requires every condition to be true.||requires at least one condition to be true.!reverses a Boolean value.- Logical operators are frequently combined with comparison operators.
Short-Circuit Evaluation
JavaScript uses short-circuit evaluation when working with logical operators. This means the JavaScript engine stops evaluating an expression as soon as the final result is already known.
Short-circuit evaluation makes code faster, prevents unnecessary function calls, and is widely used for default values and conditional execution.
Logical AND (&&)
The AND operator stops evaluating as soon as it encounters the first
false (or other falsy value).
const isLoggedIn = false;
isLoggedIn && console.log("Welcome!");
// Nothing is printed
Logical OR (||)
The OR operator stops evaluating as soon as it finds the first truthy value.
const username = "";
const displayName = username || "Guest";
console.log(displayName);
Guest
Truthy and Falsy Values
JavaScript automatically converts values to Boolean when they are used inside logical expressions.
| Falsy Values | Everything Else |
|---|---|
false0""nullundefinedNaN
|
All other values are considered truthy, including objects, arrays, non-empty strings, and non-zero numbers. |
console.log(Boolean([]));
console.log(Boolean({}));
console.log(Boolean("Hello"));
console.log(Boolean(42));
console.log(Boolean(""));
console.log(Boolean(0));
console.log(Boolean(null));
true
true
true
true
false
false
false
Empty arrays ([]) and empty objects ({})
are truthy in JavaScript. This often surprises
beginners who expect them to behave like empty strings or zero.
&&stops at the first falsy value.||stops at the first truthy value.- Only six values are falsy in JavaScript.
- Arrays and objects are always truthy, even when empty.
Conditional (Ternary) Operator
The conditional (ternary) operator is a concise alternative to an
if...else statement when choosing between two values.
It evaluates a condition and returns one value if the condition is
true and another if it is false.
| Syntax | Description |
|---|---|
condition ? valueIfTrue : valueIfFalse |
Returns one of two values depending on the condition. |
Basic Example
The ternary operator is ideal when you need to assign a value based on a simple condition.
const age = 20;
const access =
age >= 18 ? "Allowed" : "Denied";
console.log(access);
Allowed
Ternary vs if...else
Both examples below produce the same result. The ternary operator is
shorter, while an if...else statement is often easier to
read when the logic becomes more complex.
// Ternary
const status = score >= 50
? "Pass"
: "Fail";
// if...else
let result;
if (score >= 50) {
result = "Pass";
} else {
result = "Fail";
}
Use the ternary operator for short, simple decisions that return a
value. If the logic spans multiple statements or contains several
nested conditions, prefer a traditional
if...else block for better readability.
Deeply nested ternary operators are difficult to read and maintain.
If you find yourself chaining several ternaries together, rewrite
the logic using if...else or extract it into a
separate function.
// Avoid
const grade =
score >= 90 ? "A" :
score >= 80 ? "B" :
score >= 70 ? "C" :
"F";
- The ternary operator is a compact alternative to
if...else. - Syntax:
condition ? valueIfTrue : valueIfFalse. - Use it for simple assignments and return values.
- Avoid nested ternary operators when readability suffers.
Nullish Coalescing Operator (??)
The nullish coalescing operator (??) returns the value on
its left side unless that value is null or
undefined. If the left value is nullish, the value on the
right side is returned instead.
Unlike the logical OR operator (||), the nullish
coalescing operator does not replace valid values such
as 0, false, or an empty string
("").
| Operator | Returns Right Side When... |
|---|---|
?? |
The left value is null or undefined. |
Basic Example
A common use case is providing default values while preserving valid user input.
const username = null;
const displayName = username ?? "Guest";
console.log(displayName);
Guest
Comparing || and ??
Although these operators may appear similar, they behave differently when working with falsy values.
const quantity = 0;
console.log(quantity || 10);
console.log(quantity ?? 10);
10
0
| Value | value || "Default" |
value ?? "Default" |
|---|---|---|
null |
Default | Default |
undefined |
Default | Default |
0 |
Default | 0 |
false |
Default | false |
"" |
Default | "" |
Prefer ?? when assigning default values for user input,
configuration options, or API responses. It avoids accidentally
replacing valid values like 0, false, and
empty strings.
Many developers use || for default values without
realizing that it treats every falsy value as missing. This can
introduce subtle bugs when 0 or
false are legitimate values.
??only falls back fornullandundefined.||falls back for all falsy values.- Use
??when you want to preserve valid values like0andfalse.
Optional Chaining Operator (?.)
The optional chaining operator (?.) allows you to safely
access properties and methods on an object that may be
null or undefined. Instead of throwing a
runtime error, JavaScript returns undefined.
Optional chaining simplifies nested object access and is widely used when working with APIs, JSON data, and optional configuration objects.
| Syntax | Description |
|---|---|
object?.property |
Safely access an object property. |
object?.method() |
Safely call a method. |
array?.[index] |
Safely access an array element. |
Without Optional Chaining
Attempting to access a property on null or
undefined normally throws a runtime error.
const user = null;
// Throws an error
console.log(user.name);
Accessing a property on null or
undefined throws a
TypeError.
Using Optional Chaining
Optional chaining safely returns
undefined instead of throwing an error.
const user = null;
console.log(user?.name);
undefined
Working with Nested Objects
Optional chaining becomes especially useful when working with deeply nested objects where one or more properties may not exist.
const customer = {
profile: {
address: {
city: "London"
}
}
};
console.log(customer?.profile?.address?.city);
console.log(customer?.profile?.phone?.mobile);
London
undefined
Optional Method Calls
You can safely call methods that may not exist.
const user = {};
user.sayHello?.();
// No error
Optional chaining is commonly used when working with REST APIs, GraphQL responses, browser APIs, and third-party libraries where some properties may be missing.
Optional chaining prevents runtime errors, but it should not be used to hide programming mistakes. If a property is expected to exist, investigate why it is missing instead of silently ignoring the issue.
- Use
?.to safely access properties. - It returns
undefinedinstead of throwing an error. - Works with properties, methods, and arrays.
- Especially useful when consuming APIs and JSON data.
Type Operators
Type operators help you inspect values, verify object types, and check whether properties exist. They are commonly used for input validation, debugging, object-oriented programming, and working with APIs.
| Operator | Description | Returns |
|---|---|---|
typeof |
Returns the data type of a value. | String |
instanceof |
Checks whether an object was created from a constructor. | Boolean |
in |
Checks whether a property exists in an object. | Boolean |
The typeof Operator
The typeof operator returns the type of a value as a
string. It is one of the most frequently used operators when debugging
or validating user input.
console.log(typeof 42);
console.log(typeof "Hello");
console.log(typeof true);
console.log(typeof undefined);
console.log(typeof {});
number
string
boolean
undefined
object
- Validate function arguments.
- Inspect API responses.
- Debug unexpected values.
- Perform runtime type checks.
Although null represents the intentional absence of a
value, typeof null returns
"object". This is a long-standing JavaScript quirk
dating back to the earliest versions of the language.
console.log(typeof null);
object
typeofalways returns a string.- It is ideal for runtime type checking.
typeof nullreturns"object", which is a historical JavaScript behavior.
The instanceof and in Operators
The instanceof operator checks whether an object inherits
from a constructor's prototype. The in operator checks
whether a property exists in an object or anywhere in its prototype
chain.
| Operator | Purpose | Example |
|---|---|---|
instanceof |
Checks an object's prototype relationship. | value instanceof Date |
in |
Checks whether a property exists. | "name" in user |
The instanceof Operator
Use instanceof to check whether an object was created by a
particular constructor or inherits from its prototype.
const today = new Date();
const items = [];
console.log(today instanceof Date);
console.log(items instanceof Array);
console.log(items instanceof Object);
true
true
true
Use Array.isArray() instead of
value instanceof Array when checking arrays. It is more
reliable when values come from another browser window or execution
context.
The in Operator
The in operator returns true when a property
exists in an object, even when the property's value is
undefined.
const user = {
name: "Maya",
email: undefined
};
console.log("name" in user);
console.log("email" in user);
console.log("age" in user);
true
true
false
Checking object.property !== undefined is not the same
as checking whether the property exists. A property can exist while
intentionally storing undefined.
const settings = {
theme: undefined
};
console.log("theme" in settings);
console.log(settings.theme !== undefined);
true
false
The in operator also detects inherited properties. Use
Object.hasOwn(object, property) when you only want to
check properties defined directly on the object.
const user = {
name: "Maya"
};
console.log("toString" in user);
console.log(Object.hasOwn(user, "toString"));
console.log(Object.hasOwn(user, "name"));
true
false
true
instanceofchecks prototype relationships.- Prefer
Array.isArray()when checking arrays. inchecks both own and inherited properties.- Use
Object.hasOwn()for own-property checks.
Bitwise Operators
Bitwise operators work directly with the binary representation of integers. They compare or manipulate individual bits and are commonly used in low-level programming, graphics, networking, permissions, and performance-sensitive applications.
While bitwise operators are rarely needed in everyday JavaScript development, understanding them can be valuable when reading legacy code or working with specialized APIs.
| Operator | Name | Description |
|---|---|---|
& |
Bitwise AND | Sets each bit to 1 only if both bits are 1. |
| |
Bitwise OR | Sets each bit to 1 if either bit is 1. |
^ |
Bitwise XOR | Sets each bit to 1 if the bits are different. |
~ |
Bitwise NOT | Inverts every bit. |
<< |
Left Shift | Shifts bits to the left. |
>> |
Right Shift | Shifts bits to the right. |
>>> |
Unsigned Right Shift | Right shift without preserving the sign bit. |
Simple Example
Bitwise operators compare numbers one bit at a time. The examples below demonstrate the three most common operations.
console.log(5 & 3);
console.log(5 | 3);
console.log(5 ^ 3);
1
7
6
Shift Operators
Shift operators move binary digits left or right. Shifting left by one position generally doubles a number, while shifting right by one position generally halves it.
console.log(8 << 1);
console.log(8 >> 1);
16
4
- Permission and flag systems.
- Image and graphics processing.
- Compression algorithms.
- Game development.
- Low-level data manipulation.
Most web applications rarely require bitwise operators. They are considered an advanced topic and are primarily used in specialized programming scenarios.
- Bitwise operators work with binary values.
- They are mainly used in advanced programming.
- Most frontend developers rarely need them.
- Understanding them helps when reading low-level JavaScript code.
Operator Precedence
Operator precedence determines the order in which JavaScript evaluates expressions containing multiple operators. Operators with higher precedence are evaluated before operators with lower precedence, unless parentheses are used to explicitly change the order.
Understanding operator precedence helps you avoid subtle bugs and makes complex expressions easier to read and maintain.
Basic Example
const result = 10 + 5 * 2;
console.log(result);
20
Multiplication is evaluated before addition, so the expression becomes:
10 + (5 * 2)
Using Parentheses
Parentheses always take priority and should be used whenever they make an expression easier to understand.
const result = (10 + 5) * 2;
console.log(result);
30
Common Operator Precedence
| Priority | Operators | Description |
|---|---|---|
| 1 (Highest) | () |
Grouping |
| 2 | !, typeof, ++, -- |
Unary operators |
| 3 | ** |
Exponentiation |
| 4 | *, /, % |
Multiplication / Division |
| 5 | +, - |
Addition / Subtraction |
| 6 | <, >, <=, >= |
Relational operators |
| 7 | ==, ===, !=, !== |
Equality operators |
| 8 | && |
Logical AND |
| 9 | ||, ?? |
Logical OR / Nullish Coalescing |
| 10 | ?: |
Ternary operator |
| 11 | =, +=, ??= |
Assignment operators |
Most experienced JavaScript developers do not memorize the complete precedence table. Instead, they use parentheses whenever an expression could be misunderstood.
Even if you know the precedence rules, adding parentheses often improves readability and makes your intentions clear to other developers.
- Parentheses always have the highest priority.
- Multiplication is evaluated before addition.
- Comparison operators run before logical operators.
- Assignment operators are evaluated near the end.
- Use parentheses to make complex expressions easier to read.
JavaScript Operators Quick Reference
This quick reference summarizes the most commonly used JavaScript operators. Use it to quickly review syntax, purpose, and typical behavior without reading the complete explanations above.
Arithmetic Operators
| Operator | Name | Example | Result |
|---|---|---|---|
+ |
Addition | 10 + 5 |
15 |
- |
Subtraction | 10 - 5 |
5 |
* |
Multiplication | 10 * 5 |
50 |
/ |
Division | 10 / 5 |
2 |
% |
Remainder | 10 % 3 |
1 |
** |
Exponentiation | 2 ** 3 |
8 |
++ |
Increment | value++ |
Adds one |
-- |
Decrement | value-- |
Subtracts one |
Assignment Operators
| Operator | Purpose | Equivalent Expression |
|---|---|---|
= |
Assign a value | x = 10 |
+= |
Add and assign | x = x + y |
-= |
Subtract and assign | x = x - y |
*= |
Multiply and assign | x = x * y |
/= |
Divide and assign | x = x / y |
%= |
Remainder and assign | x = x % y |
**= |
Exponentiate and assign | x = x ** y |
Comparison Operators
| Operator | Name | Example | Result |
|---|---|---|---|
=== |
Strict equality | 5 === 5 |
true |
!== |
Strict inequality | 5 !== "5" |
true |
== |
Loose equality | 5 == "5" |
true |
!= |
Loose inequality | 5 != "5" |
false |
> |
Greater than | 10 > 5 |
true |
< |
Less than | 5 < 10 |
true |
>= |
Greater than or equal | 10 >= 10 |
true |
<= |
Less than or equal | 5 <= 10 |
true |
Logical Operators
| Operator | Name | Behavior |
|---|---|---|
&& |
Logical AND | Returns the first falsy value or the final value. |
|| |
Logical OR | Returns the first truthy value or the final value. |
! |
Logical NOT | Converts a value to Boolean and reverses it. |
?? |
Nullish coalescing | Uses the fallback only for null or undefined. |
&&= |
Logical AND assignment | Assigns when the current value is truthy. |
||= |
Logical OR assignment | Assigns when the current value is falsy. |
??= |
Nullish assignment | Assigns when the current value is nullish. |
Prefer === and !== in modern JavaScript.
Loose equality operators perform implicit type conversion and can
produce unexpected results.
JavaScript Operators Quick Reference – Part 2
This section completes the JavaScript operators reference with conditional, optional chaining, type, property, bitwise, and special operators.
Conditional and Access Operators
| Operator | Name | Example | Purpose |
|---|---|---|---|
?: |
Ternary operator | age >= 18 ? "Adult" : "Minor" |
Returns one of two values based on a condition. |
?. |
Optional chaining | user?.profile?.name |
Safely accesses a property, method, or array element. |
?? |
Nullish coalescing | username ?? "Guest" |
Provides a fallback for null or undefined. |
Type and Property Operators
| Operator | Name | Example | Returns |
|---|---|---|---|
typeof |
Type operator | typeof value |
A string describing the value type. |
instanceof |
Prototype relationship | date instanceof Date |
true or false. |
in |
Property existence | "name" in user |
true if the property exists. |
delete |
Property deletion | delete user.age |
true when the deletion operation succeeds. |
Bitwise Operators
| Operator | Name | Example |
|---|---|---|
& |
Bitwise AND | 5 & 3 |
| |
Bitwise OR | 5 | 3 |
^ |
Bitwise XOR | 5 ^ 3 |
~ |
Bitwise NOT | ~5 |
<< |
Left shift | 8 << 1 |
>> |
Signed right shift | 8 >> 1 |
>>> |
Unsigned right shift | -8 >>> 1 |
Special Operators
| Operator | Purpose | Example |
|---|---|---|
new |
Creates an object instance from a constructor or class. | new Date() |
void |
Evaluates an expression and returns undefined. |
void 0 |
, |
Evaluates multiple expressions and returns the final result. | (first(), second()) |
await |
Waits for a Promise inside an async context. | await fetchData() |
yield |
Pauses and resumes a generator function. | yield value |
Operators such as void and the comma operator are valid
JavaScript, but they are uncommon in application code and can reduce
readability. Prefer clearer alternatives unless the behavior is
intentional and well documented.
- Use
?.for safe property access. - Use
??for nullish fallback values. - Use
typeof,instanceof, andinfor runtime checks. - Bitwise operators are primarily used in specialized code.
- Prefer readable alternatives to uncommon special operators.
Operator Best Practices
Knowing how operators work is only part of writing high-quality JavaScript. Equally important is knowing when to use them and how to write expressions that are predictable, readable, and easy to maintain.
1. Prefer Strict Equality
Always use === and !== unless you have a
specific reason to allow type coercion.
if (userAge === 18) {
console.log("Adult");
}
Strict equality prevents implicit type conversion, making your code more predictable and easier to debug.
2. Use Parentheses for Clarity
Even if you know operator precedence, parentheses improve readability and make complex expressions easier to understand.
// Good
const total = (price + tax) * quantity;
// Harder to read
const total = price + tax * quantity;
3. Use ?? Instead of || for Default Values
When 0, false, or an empty string are valid
values, prefer ?? over ||.
const quantity = 0;
const value = quantity ?? 1;
4. Keep Logical Expressions Simple
Break long conditions into descriptive variables instead of creating difficult-to-read expressions.
const isAdult = age >= 18;
const hasPermission = user.role === "admin";
if (isAdult && hasPermission) {
// ...
}
5. Avoid Nested Ternary Operators
Nested ternary expressions quickly become difficult to read. Use
if...else when multiple branches are required.
const grade =
score >= 90 ? "A" :
score >= 80 ? "B" :
score >= 70 ? "C" :
"F";
6. Use Optional Chaining Carefully
Optional chaining prevents runtime errors, but it should not hide bugs. If a property should always exist, investigate why it is missing instead of silently ignoring the problem.
Optional chaining is excellent for API responses, configuration objects, and optional user data.
7. Write for Humans First
JavaScript offers many clever shortcuts, but the clearest solution is usually the best one. Prioritize readability over writing fewer characters.
- ✔ Prefer
===and!==. - ✔ Use parentheses when expressions become complex.
- ✔ Prefer
??for default values. - ✔ Keep logical expressions readable.
- ✔ Avoid deeply nested ternary operators.
- ✔ Use optional chaining intentionally.
- ✔ Optimize for readability, not cleverness.
Common Operator Mistakes
JavaScript operators are powerful, but a few common mistakes can lead to unexpected behavior. Understanding these pitfalls will help you write safer, more predictable, and easier-to-maintain code.
1. Using == Instead of ===
The loose equality operator performs automatic type conversion before comparing values. This often produces unexpected results.
console.log(5 == "5");
true
console.log(5 === "5");
2. Using || for Default Values
The logical OR operator treats every falsy value as missing, including
0, false, and empty strings.
const quantity = 0;
console.log(quantity || 10);
10
console.log(quantity ?? 10);
3. Forgetting Operator Precedence
JavaScript evaluates multiplication before addition. Without parentheses, expressions may produce unexpected results.
const total = 10 + 5 * 2;
console.log(total);
20
const total = (10 + 5) * 2;
4. Assuming Empty Arrays Are Falsy
Empty arrays and empty objects are still truthy values in JavaScript.
console.log(Boolean([]));
console.log(Boolean({}));
true
true
Only six values are falsy:
false,
0,
"",
null,
undefined,
and NaN.
5. Overusing Nested Ternary Operators
Deeply nested ternary operators quickly become difficult to read and maintain.
// Hard to read
const grade =
score >= 90 ? "A" :
score >= 80 ? "B" :
score >= 70 ? "C" :
"F";
Replace complex nested ternary expressions with
if...else statements or a dedicated function.
6. Forgetting That typeof null Returns "object"
This behavior has existed since the earliest versions of JavaScript and is maintained for backward compatibility.
console.log(typeof null);
object
- Avoid
==unless type coercion is intentional. - Prefer
??over||for default values. - Use parentheses to make evaluation order obvious.
- Remember that empty arrays and objects are truthy.
- Avoid deeply nested ternary operators.
- Don't forget that
typeof nullreturns"object".
Control Flow and Conditional Statements
Control flow determines which parts of a JavaScript program run, when they run, and under which conditions. Conditional statements let your code make decisions by executing different blocks based on Boolean expressions.
What Is Control Flow?
JavaScript normally executes statements from top to bottom, one line at a time. Control flow statements change this default sequence by allowing a program to choose between different execution paths.
Conditions are evaluated as either true or
false. Depending on the result, JavaScript can execute one
block, skip another block, or select between several possible actions.
Conditionals Quick Reference
The following table summarizes the main conditional structures used in modern JavaScript.
| Statement | Purpose | Typical Use | Example |
|---|---|---|---|
if |
Runs code when one condition is truthy. | Single decision | if (age >= 18) { ... } |
if...else |
Chooses between two execution paths. | Either-or decisions | if (isOnline) { ... } else { ... } |
else if |
Tests multiple conditions in sequence. | Several possible outcomes | else if (score >= 80) { ... } |
switch |
Matches one value against multiple cases. | Known discrete values | switch (role) { case "admin": ... } |
| Nested conditions | Places one conditional inside another. | Dependent decisions | if (user) { if (user.active) { ... } } |
| Guard clause | Exits early when a requirement is not met. | Reducing nesting | if (!user) return; |
| Ternary operator | Returns one of two values from an expression. | Short value selection | const label = active ? "On" : "Off"; |
if, else if, or
switch.
Basic Control Flow Example
This example checks a user's account state and selects a message based on the first matching condition.
const user = {
name: "Maya",
isLoggedIn: true,
isAdmin: false
};
let message;
if (!user.isLoggedIn) {
message = "Please sign in.";
} else if (user.isAdmin) {
message = `Welcome to the admin dashboard, ${user.name}.`;
} else {
message = `Welcome back, ${user.name}.`;
}
console.log(message);
Welcome back, Maya.
How JavaScript Evaluates the Conditions
-
JavaScript first evaluates
!user.isLoggedIn. -
Because
user.isLoggedInistrue, the negated expression becomesfalse. -
JavaScript skips the first block and checks
user.isAdmin. -
Because
user.isAdminis alsofalse, JavaScript skips the second block. -
The final
elseblock runs because none of the previous conditions matched.
if...else if...else chain, JavaScript stops as soon as
it finds the first truthy condition. More specific conditions should
usually appear before broader conditions.
What This Chapter Covers
The following sections explain how to write clear, predictable, and maintainable conditional logic:
- Writing reliable
ifstatements - Choosing between
if...elseexecution paths - Handling several outcomes with
else if - Using nested conditions without harming readability
- Selecting known values with
switch - Simplifying functions with guard clauses
- Applying practical conditional patterns
- Avoiding common control-flow mistakes
Control Flow Summary
Conditional statements make JavaScript programs responsive to data, user actions, application state, and external events. The main goal is not merely to make conditions work, but to make every possible code path easy to understand and maintain.
JavaScript if Statements
The if statement executes a block of code only when its
condition evaluates to a truthy value. It is the simplest and most
commonly used conditional statement in JavaScript.
Basic if Statement Syntax
An if statement contains a condition inside parentheses,
followed by a code block inside curly braces.
if (condition) {
// Runs only when the condition is truthy
}
JavaScript evaluates the expression inside the parentheses. When the result is truthy, the code block runs. When the result is falsy, the block is skipped.
Basic if Statement Example
This example displays a notification only when the number of unread messages is greater than zero.
const unreadMessages = 3;
if (unreadMessages > 0) {
console.log(`You have ${unreadMessages} unread messages.`);
}
You have 3 unread messages.
Checking Boolean Values
When a variable already contains a Boolean value, it can be used directly
as the condition. Comparing it explicitly with true is
unnecessary.
const isEmailVerified = true;
if (isEmailVerified) {
console.log("Your email address is verified.");
}
Your email address is verified.
if (isEmailVerified) instead of
if (isEmailVerified === true) when the variable is already
guaranteed to contain a Boolean value.
Combining Multiple Conditions
Logical operators can combine several requirements inside one
if statement.
| Operator | Meaning | Condition Runs When |
|---|---|---|
&& |
Logical AND | Every condition is truthy |
|| |
Logical OR | At least one condition is truthy |
! |
Logical NOT | The original condition is falsy |
const user = {
isLoggedIn: true,
hasSubscription: true,
accountSuspended: false
};
if (
user.isLoggedIn &&
user.hasSubscription &&
!user.accountSuspended
) {
console.log("Premium content unlocked.");
}
Premium content unlocked.
Using Truthy and Falsy Values
An if condition does not have to produce the literal Boolean
value true. JavaScript automatically converts the evaluated
value to a Boolean.
The following values are falsy:
false0and-00n"",'', and empty template stringsnullundefinedNaN
Nearly every other JavaScript value is truthy, including empty arrays and empty objects.
const username = "Avery";
if (username) {
console.log(`Signed in as ${username}.`);
}
Signed in as Avery.
if (items) does not check whether an array contains items.
Use if (items.length > 0) instead.
Prefer Explicit Checks When Zero Is Valid
A truthiness check can be incorrect when valid data may contain
0, an empty string, or another falsy value.
const product = {
name: "JavaScript Course",
discountPercent: 0
};
if (product.discountPercent !== undefined) {
console.log(`Discount: ${product.discountPercent}%`);
}
Discount: 0%
0 or
an empty string are valid application data.
if Statement Summary
Use an if statement when a block should run only under a
specific condition. Keep conditions readable, use strict comparisons
where appropriate, combine related requirements carefully, and avoid
relying on truthiness when valid values may be falsy.
JavaScript Strings
JavaScript strings represent textual data. They are used for names, messages, URLs, HTML content, form input, API data, and nearly every other type of text handled by a JavaScript application.
Strings are primitive and immutable values. String methods never modify the original string; they return a new value instead.
Creating Strings
JavaScript supports single quotes, double quotes, and template literals.
| Syntax | Example | Best Used For |
|---|---|---|
| Single quotes | 'Hello' |
Ordinary text values. |
| Double quotes | "Hello" |
Ordinary text values. |
| Template literals | `Hello` |
Interpolation and multiline strings. |
const first = "JavaScript";
const second = 'Cheat Sheet';
const third = `Learn ${first}`;
console.log(first);
console.log(second);
console.log(third);
JavaScript
Cheat Sheet
Learn JavaScript
Strings Are Immutable
Individual characters cannot be changed directly. You must create and assign a new string.
let language = "JavaScript";
language[0] = "X";
console.log(language);
language = "TypeScript";
console.log(language);
JavaScript
TypeScript
Reassigning a variable is allowed when it was declared with
let. The original string value itself is still immutable.
String Length
The length property returns the number of UTF-16 code units in
a string. For ordinary English text, this normally matches the visible
character count.
const language = "JavaScript";
console.log(language.length);
10
Accessing Characters
Use bracket notation or at() to retrieve individual
characters.
| Syntax | Description | Negative Index? |
|---|---|---|
text[0] |
Access a character by zero-based index. | No |
text.charAt(0) |
Return the character at an index. | No |
text.at(-1) |
Access from the beginning or end. | Yes |
const word = "JavaScript";
console.log(word[0]);
console.log(word.charAt(4));
console.log(word.at(-1));
J
S
t
Template Literals
Template literals use backticks and allow expressions to be inserted with
${expression}.
const name = "Alice";
const lessons = 12;
const message =
`${name} completed ${lessons} lessons.`;
console.log(message);
Alice completed 12 lessons.
Multiline Strings
Template literals can span multiple lines without escape sequences.
const message = `JavaScript
Strings
Cheat Sheet`;
console.log(message);
JavaScript
Strings
Cheat Sheet
Combining Strings
Strings can be combined with the + operator, template
literals, or concat(). Template literals are usually the most
readable option when variables are involved.
const firstName = "Alice";
const lastName = "Johnson";
const fullName =
firstName + " " + lastName;
const modernName =
`${firstName} ${lastName}`;
console.log(fullName);
console.log(modernName);
Alice Johnson
Alice Johnson
Changing Letter Case
| Method | Purpose | Example Result |
|---|---|---|
toUpperCase() |
Convert letters to uppercase. | "HELLO" |
toLowerCase() |
Convert letters to lowercase. | "hello" |
toLocaleUpperCase() |
Uppercase using locale rules. | Locale-dependent |
toLocaleLowerCase() |
Lowercase using locale rules. | Locale-dependent |
const text = "JavaScript";
console.log(text.toUpperCase());
console.log(text.toLowerCase());
console.log(text);
JAVASCRIPT
javascript
JavaScript
Removing Whitespace
| Method | Description |
|---|---|
trim() |
Remove whitespace from both ends. |
trimStart() |
Remove whitespace from the beginning. |
trimEnd() |
Remove whitespace from the end. |
const input = " Alice ";
console.log(input);
console.log(input.trim());
Alice
Alice
Searching Inside Strings
| Method | Returns | Case-Sensitive? |
|---|---|---|
includes() |
true or false |
Yes |
startsWith() |
true or false |
Yes |
endsWith() |
true or false |
Yes |
indexOf() |
First matching index or -1 |
Yes |
lastIndexOf() |
Last matching index or -1 |
Yes |
search() |
Match index or -1 |
Depends on pattern |
const text =
"Learn JavaScript today";
console.log(
text.includes("JavaScript")
);
console.log(
text.startsWith("Learn")
);
console.log(
text.endsWith("today")
);
console.log(
text.indexOf("JavaScript")
);
true
true
true
6
Extracting Parts of a String
| Method | Syntax | Notes |
|---|---|---|
slice() |
text.slice(start, end) |
Supports negative indexes. |
substring() |
text.substring(start, end) |
Negative values become zero. |
substr() |
text.substr(start, length) |
Legacy method; avoid in new code. |
const language = "JavaScript";
console.log(
language.slice(0, 4)
);
console.log(
language.slice(4)
);
console.log(
language.slice(-6)
);
Java
Script
Script
Replacing Text
Use replace() for one match and replaceAll() for
every matching substring.
const sentence =
"JavaScript is fun. JavaScript is useful.";
console.log(
sentence.replace(
"JavaScript",
"TypeScript"
)
);
console.log(
sentence.replaceAll(
"JavaScript",
"TypeScript"
)
);
TypeScript is fun. JavaScript is useful.
TypeScript is fun. TypeScript is useful.
Repeating and Padding Strings
| Method | Purpose | Example |
|---|---|---|
repeat() |
Repeat a string a specified number of times. | "-".repeat(5) |
padStart() |
Add characters to the beginning. | "5".padStart(2, "0") |
padEnd() |
Add characters to the end. | "5".padEnd(3, "0") |
console.log(
"-".repeat(10)
);
console.log(
"7".padStart(3, "0")
);
console.log(
"JS".padEnd(5, ".")
);
----------
007
JS...
Core String Methods Quick Reference
| Method or Property | Purpose |
|---|---|
length |
Return string length. |
at() |
Return a character using a positive or negative index. |
includes() |
Check whether text exists. |
startsWith() |
Check the beginning of a string. |
endsWith() |
Check the end of a string. |
indexOf() |
Find the first matching position. |
slice() |
Extract part of a string. |
replace() |
Replace one match. |
replaceAll() |
Replace all matches. |
trim() |
Remove surrounding whitespace. |
toUpperCase() |
Convert text to uppercase. |
toLowerCase() |
Convert text to lowercase. |
repeat() |
Repeat text. |
padStart() |
Pad the beginning of a string. |
padEnd() |
Pad the end of a string. |
String methods return new strings. Remember to store or use the returned result when you need the changed value.
let name = " Alice ";
name.trim();
console.log(name); // Still contains spaces
name = name.trim();
console.log(name); // "Alice"
Prefer template literals when combining variables with text, use
trim() when processing user input, and normalize letter case
before performing case-insensitive comparisons.
Strings Core Summary
- Strings represent textual data.
- Strings are immutable primitive values.
- Template literals support interpolation and multiline text.
- Use
length, bracket notation, orat()to inspect characters. - Use search methods to locate text.
- Use
slice()to extract substrings. - Use
replace()orreplaceAll()to replace content. - String methods return new strings rather than changing the original.
Advanced JavaScript String Methods
JavaScript includes powerful methods for splitting, matching, comparing, transforming, and validating text. These features are commonly used when processing form input, URLs, API responses, search queries, filenames, and application data.
This section completes the JavaScript strings reference with advanced methods, regular expressions, Unicode handling, practical patterns, and important best practices.
Splitting a String into an Array
The split() method divides a string at each matching separator
and returns an array.
const languages =
"JavaScript,Python,SQL";
const result =
languages.split(",");
console.log(result);
["JavaScript", "Python", "SQL"]
Common split() Patterns
| Example | Result | Purpose |
|---|---|---|
"a,b,c".split(",") |
["a", "b", "c"] |
Split comma-separated data. |
"Hello world".split(" ") |
["Hello", "world"] |
Split text into words. |
"JavaScript".split("") |
Array of code units | Split into individual units. |
"a-b-c".split("-", 2) |
["a", "b"] |
Limit the number of returned items. |
Joining Strings After Processing
Because split() returns an array, it is often combined with
array methods and join().
const title =
"javascript string methods";
const formatted = title
.split(" ")
.map(word =>
word[0].toUpperCase() +
word.slice(1)
)
.join(" ");
console.log(formatted);
JavaScript String Methods
Matching Text
JavaScript provides several methods for matching strings with regular expressions.
| Method | Returns | Best Used For |
|---|---|---|
match() |
Match information or null |
Finding one match or all global matches. |
matchAll() |
An iterator of detailed matches | Finding every match with groups and indexes. |
search() |
First match index or -1 |
Finding where a pattern first appears. |
RegExp.test() |
true or false |
Checking whether a pattern exists. |
const text =
"Order 12 contains 3 products";
const numbers =
text.match(/\d+/g);
console.log(numbers);
["12", "3"]
Using matchAll()
The matchAll() method returns an iterator containing detailed
information about every match. The regular expression must use the global
g flag.
const text =
"JavaScript 2025, Python 2026";
const matches =
text.matchAll(
/([A-Za-z]+) (\d{4})/g
);
for (const match of matches) {
console.log(
match[1],
match[2],
match.index
);
}
JavaScript 2025 0
Python 2026 17
Regular Expression Flags
| Flag | Name | Effect |
|---|---|---|
g |
Global | Find every match instead of only the first. |
i |
Case-insensitive | Ignore uppercase and lowercase differences. |
m |
Multiline | Make line anchors work on multiple lines. |
s |
DotAll | Allow the dot to match newline characters. |
u |
Unicode | Enable Unicode-aware pattern behavior. |
y |
Sticky | Match from the current lastIndex position. |
Case-Insensitive Search
const text =
"Learn JavaScript";
console.log(
/javascript/i.test(text)
);
console.log(
text.search(/javascript/i)
);
true
6
Replacing Text with Regular Expressions
Regular expressions make it possible to replace patterns rather than exact text values.
const phone =
"555 123 4567";
const formatted =
phone.replace(
/\s+/g,
"-"
);
console.log(formatted);
555-123-4567
Replacement Functions
The second argument to replace() may be a function. The
function receives the matched text and returns its replacement.
const text =
"Products: 5, price: 20";
const result =
text.replace(
/\d+/g,
match =>
String(Number(match) * 2)
);
console.log(result);
Products: 10, price: 40
Comparing Strings
Equality operators compare string values exactly, including capitalization and whitespace.
console.log(
"JavaScript" === "JavaScript"
);
console.log(
"JavaScript" === "javascript"
);
console.log(
"Hello" === "Hello "
);
true
false
false
Case-Insensitive Comparison
Normalize both values before comparing them.
const first =
" JavaScript ";
const second =
"javascript";
const isEqual =
first.trim().toLowerCase() ===
second.trim().toLowerCase();
console.log(isEqual);
true
Locale-Aware Comparison
The localeCompare() method compares strings according to
language-specific sorting rules.
const names = [
"Östen",
"Anna",
"Åke",
"Älva"
];
names.sort((first, second) =>
first.localeCompare(
second,
"sv"
)
);
console.log(names);
["Anna", "Åke", "Älva", "Östen"]
localeCompare() Return Values
| Result | Meaning |
|---|---|
| Negative number | The first string sorts before the second. |
0 |
The strings are equivalent for the comparison. |
| Positive number | The first string sorts after the second. |
The specification guarantees only a negative number, zero, or a positive
number. Do not write code that depends on the exact values
-1 or 1.
Converting Values to Strings
| Technique | Example | Notes |
|---|---|---|
String(value) |
String(42) |
Safe explicit conversion. |
value.toString() |
(42).toString() |
Fails for null and undefined. |
| Template literal | `${value}` |
Useful while building text. |
| Concatenation | value + "" |
Implicit and generally less clear. |
const number = 42;
const active = true;
const missing = null;
console.log(
String(number)
);
console.log(
String(active)
);
console.log(
String(missing)
);
42
true
null
Escape Sequences
Escape sequences represent characters that are difficult to type directly inside a quoted string.
| Sequence | Meaning |
|---|---|
\n |
New line |
\t |
Horizontal tab |
\\ |
Backslash |
\" |
Double quote |
\' |
Single quote |
\uXXXX |
Unicode code unit |
\u{XXXXX} |
Unicode code point |
const message =
"First line\nSecond line";
const quote =
"She said, \"Hello!\"";
const path =
"C:\\Users\\Alice";
console.log(message);
console.log(quote);
console.log(path);
First line
Second line
She said, "Hello!"
C:\Users\Alice
Raw Strings
The String.raw tag returns the raw form of a template literal,
preserving backslashes.
const path =
String.raw`C:\Users\Alice`;
console.log(path);
C:\Users\Alice
Unicode and Emoji
JavaScript strings use UTF-16. Some visible characters, including many
emoji, require two code units and may produce surprising results with
length or bracket indexing.
const emoji = "😊";
console.log(
emoji.length
);
console.log(
[...emoji].length
);
2
1
Spread syntax, Array.from(), and
for...of iterate by Unicode code point and handle many emoji
more accurately than split("").
Iterating Over a String
const text = "A😊B";
for (const character of text) {
console.log(character);
}
A
😊
B
Unicode Code Points
const emoji = "😊";
const codePoint =
emoji.codePointAt(0);
console.log(codePoint);
console.log(
String.fromCodePoint(codePoint)
);
128522
😊
Unicode Normalization
Visually identical text may use different Unicode sequences. The
normalize() method converts them to a consistent form.
const first = "\u00E9";
const second = "e\u0301";
console.log(
first === second
);
console.log(
first.normalize() ===
second.normalize()
);
false
true
Tagged Template Literals
A tagged template passes the static string parts and inserted values to a function. Libraries may use this feature for formatting, localization, and query construction.
function highlight(
strings,
...values
) {
return strings.reduce(
(result, text, index) =>
result +
text +
(
index < values.length
? `[${values[index]}]`
: ""
),
""
);
}
const language = "JavaScript";
const level = "Beginner";
const result =
highlight`${language} level: ${level}`;
console.log(result);
[JavaScript] level: [Beginner]
String Objects vs Primitive Strings
Normal strings are primitive values. The String constructor
can create wrapper objects, but these should normally be avoided.
const primitive =
"JavaScript";
const object =
new String("JavaScript");
console.log(
typeof primitive
);
console.log(
typeof object
);
console.log(
primitive === object
);
string
object
false
Use String(value) for conversion, not
new String(value). Wrapper objects behave differently during
equality checks and conditional evaluation.
Practical Pattern: Create a Slug
A URL slug commonly uses lowercase letters, hyphens, and normalized whitespace.
function createSlug(title) {
return title
.trim()
.toLowerCase()
.replace(
/[^a-z0-9]+/g,
"-"
)
.replace(
/^-|-$/g,
""
);
}
console.log(
createSlug(
" JavaScript String Methods! "
)
);
javascript-string-methods
Practical Pattern: Mask Sensitive Text
function maskCard(number) {
const text =
String(number);
return text
.slice(-4)
.padStart(
text.length,
"*"
);
}
console.log(
maskCard("1234567812345678")
);
************5678
Practical Pattern: Count Words
function countWords(text) {
const cleaned =
text.trim();
if (!cleaned) {
return 0;
}
return cleaned
.split(/\s+/)
.length;
}
console.log(
countWords(
"Learn modern JavaScript today"
)
);
4
Practical Pattern: Truncate Text
function truncate(
text,
maximumLength
) {
if (
text.length <=
maximumLength
) {
return text;
}
return (
text.slice(
0,
maximumLength - 3
) + "..."
);
}
console.log(
truncate(
"JavaScript string methods",
18
)
);
JavaScript str...
Common String Mistakes
| Mistake | Problem | Better Approach |
|---|---|---|
| Ignoring returned values | String methods do not mutate the original. | Store or immediately use the returned string. |
Using new String() |
Creates an object rather than a primitive. | Use string literals or String(). |
| Case-sensitive comparison by accident | Equivalent user input may fail comparison. | Normalize case and whitespace first. |
Using split("") for emoji |
May split surrogate pairs incorrectly. | Use spread syntax or Array.from(). |
Building complex text with many + operators |
Becomes difficult to read. | Use template literals. |
Assuming replace() replaces every match |
Plain-string replacement changes only the first match. | Use replaceAll() or a global regular expression. |
| Using regex for simple exact checks | Adds unnecessary complexity. | Use includes(), startsWith(), or endsWith(). |
| Trusting client-side validation alone | Users can bypass browser JavaScript. | Validate and sanitize again on the server. |
String Best Practices
- Use template literals for readable interpolation.
- Use
trim()when processing user input. - Normalize strings before case-insensitive comparison.
- Prefer specific string methods over unnecessary regular expressions.
- Use
localeCompare()for user-facing alphabetical sorting. - Remember that strings are immutable.
- Use Unicode-aware iteration when text may contain emoji.
- Escape or sanitize untrusted content before inserting it into HTML.
- Use descriptive variable names for transformed strings.
- Keep complex string-processing logic inside reusable functions.
Advanced String Methods Quick Reference
| Method | Purpose |
|---|---|
split() |
Convert a string into an array. |
match() |
Return regular-expression matches. |
matchAll() |
Iterate over detailed global matches. |
search() |
Find the first pattern position. |
localeCompare() |
Compare strings using locale rules. |
normalize() |
Normalize Unicode sequences. |
codePointAt() |
Return a Unicode code point. |
String.fromCodePoint() |
Create text from Unicode code points. |
String.raw |
Preserve raw backslashes in a template. |
String() |
Explicitly convert a value to a string. |
Do not insert untrusted strings directly with innerHTML.
Prefer textContent for plain text and sanitize content when
HTML is intentionally allowed.
JavaScript Strings Chapter Complete
- Strings are immutable primitive text values.
- Template literals support interpolation and multiline content.
- String methods return new values rather than mutating the original.
split()converts text into arrays for further processing.- Regular expressions support advanced matching and replacement.
localeCompare()provides language-aware sorting.- JavaScript strings use UTF-16 and require care with emoji and Unicode.
- Normalization helps compare equivalent Unicode sequences.
- Reusable helper functions simplify common text transformations.
- User-provided strings must be handled safely before HTML insertion.
JavaScript Arrays
JavaScript arrays store ordered collections of values. A single array can contain strings, numbers, booleans, objects, other arrays, functions, or a mixture of several data types.
Arrays use zero-based indexing, which means the first element has the
index 0. They are one of the most commonly used data
structures in JavaScript and are essential for processing lists,
collections, API responses, tables, menus, and application data.
Creating an Array
The recommended way to create an array is with array literal syntax using square brackets.
const languages = [
"JavaScript",
"Python",
"SQL"
];
console.log(languages);
["JavaScript", "Python", "SQL"]
Array Literal vs Array Constructor
| Technique | Example | Recommendation |
|---|---|---|
| Array literal | const items = []; |
Recommended for most arrays. |
| Array constructor | const items = new Array(); |
Valid, but usually unnecessary. |
Array.of() |
Array.of(5) |
Create an array containing supplied values. |
Array.from() |
Array.from("ABC") |
Create an array from an iterable or array-like value. |
Passing one number to new Array() creates an empty array with
that length rather than an array containing the number.
const first = new Array(3);
const second = Array.of(3);
console.log(first);
console.log(second);
// first
[empty × 3]
// second
[3]
Arrays Can Store Different Data Types
JavaScript does not require every element in an array to have the same data type.
const mixedValues = [
"JavaScript",
42,
true,
null,
{ level: "Beginner" },
["HTML", "CSS"]
];
console.log(mixedValues);
Mixed arrays are valid, but arrays containing consistent types are often easier to understand, validate, and process.
Zero-Based Indexing
Every array element has a numeric position called an index. Indexes start at
0, not 1.
| Element | Index |
|---|---|
"JavaScript" |
0 |
"Python" |
1 |
"SQL" |
2 |
const languages = [
"JavaScript",
"Python",
"SQL"
];
console.log(languages[0]);
console.log(languages[1]);
console.log(languages[2]);
JavaScript
Python
SQL
Accessing a Missing Index
Reading an index that does not exist returns undefined.
const colors = [
"red",
"green",
"blue"
];
console.log(colors[10]);
undefined
The length Property
The length property returns the number of positions in an
array.
const languages = [
"JavaScript",
"Python",
"SQL"
];
console.log(
languages.length
);
3
Accessing the Last Element
Subtract one from length or use at(-1) to retrieve
the final element.
const languages = [
"JavaScript",
"Python",
"SQL"
];
console.log(
languages[
languages.length - 1
]
);
console.log(
languages.at(-1)
);
SQL
SQL
Using at()
The at() method supports both positive and negative indexes.
| Expression | Result |
|---|---|
items.at(0) |
First element |
items.at(1) |
Second element |
items.at(-1) |
Last element |
items.at(-2) |
Second-to-last element |
Updating Array Elements
Assign a new value to an existing index to replace that element.
const languages = [
"JavaScript",
"Python",
"SQL"
];
languages[1] =
"TypeScript";
console.log(languages);
["JavaScript", "TypeScript", "SQL"]
Why const Arrays Can Change
A variable declared with const cannot be reassigned to another
array, but the contents of the existing array can still be modified.
const colors = [
"red",
"green"
];
colors[0] = "blue";
console.log(colors);
["blue", "green"]
const colors = ["red"];
// Not allowed
colors = ["blue"];
The final assignment throws a TypeError because the
colors variable cannot point to a different array.
Adding an Element by Index
Assigning a value to the next available index adds an element, although
push() is usually clearer.
const languages = [
"JavaScript",
"Python"
];
languages[
languages.length
] = "SQL";
console.log(languages);
["JavaScript", "Python", "SQL"]
Avoid Creating Empty Slots
Assigning a value far beyond the current length creates empty positions in the array.
const values = [
"A",
"B"
];
values[5] = "F";
console.log(values);
console.log(values.length);
["A", "B", empty × 3, "F"]
6
Empty array slots do not behave exactly like explicit
undefined values. Avoid sparse arrays unless their behavior
is intentionally required.
Checking Whether a Value Is an Array
Use Array.isArray(). The typeof operator returns
"object" for arrays.
const languages = [
"JavaScript",
"Python"
];
console.log(
Array.isArray(languages)
);
console.log(
typeof languages
);
true
object
Creating Arrays with Array.from()
The Array.from() method converts iterable and array-like values
into real arrays.
const characters =
Array.from("JavaScript");
console.log(characters);
["J", "a", "v", "a", "S", "c", "r", "i", "p", "t"]
Array.from() with a Mapping Function
An optional second argument transforms each value while the array is being created.
const numbers =
Array.from(
{ length: 5 },
(_, index) =>
index + 1
);
console.log(numbers);
[1, 2, 3, 4, 5]
Copying an Array
Spread syntax and slice() create a shallow copy of an array.
const original = [
"JavaScript",
"Python"
];
const spreadCopy = [
...original
];
const sliceCopy =
original.slice();
spreadCopy.push("SQL");
console.log(original);
console.log(spreadCopy);
console.log(sliceCopy);
["JavaScript", "Python"]
["JavaScript", "Python", "SQL"]
["JavaScript", "Python"]
Shallow Copy Limitation
A shallow copy creates a new outer array, but nested objects and arrays remain shared by reference.
const original = [
{
name: "Alice"
}
];
const copy = [
...original
];
copy[0].name = "Maya";
console.log(
original[0].name
);
console.log(
copy[0].name
);
Maya
Maya
Spread syntax copies only the outer array. Use
structuredClone() when a supported value requires a deeper
independent copy.
Combining Arrays with Spread Syntax
const frontend = [
"HTML",
"CSS"
];
const programming = [
"JavaScript",
"Python"
];
const skills = [
...frontend,
...programming
];
console.log(skills);
["HTML", "CSS", "JavaScript", "Python"]
Nested Arrays
An array can contain other arrays, creating multidimensional structures.
const grid = [
["A1", "A2"],
["B1", "B2"],
["C1", "C2"]
];
console.log(grid[0][1]);
console.log(grid[2][0]);
A2
C1
Arrays of Objects
Real applications frequently represent records as an array of objects.
const users = [
{
id: 1,
name: "Alice",
active: true
},
{
id: 2,
name: "Bob",
active: false
}
];
console.log(
users[0].name
);
console.log(
users[1].active
);
Alice
false
Basic Array Reference
| Syntax or Method | Purpose |
|---|---|
[] |
Create an array literal. |
array[index] |
Access or update an element. |
array.length |
Return the array length. |
array.at() |
Access an element with positive or negative indexing. |
Array.isArray() |
Check whether a value is an array. |
Array.of() |
Create an array from supplied arguments. |
Array.from() |
Create an array from an iterable or array-like value. |
[...array] |
Create a shallow copy or combine arrays. |
array.slice() |
Create a shallow copy or extract a section. |
Use array literals for creation, at(-1) for readable access
to the last element, Array.isArray() for type checks, and
spread syntax when creating shallow copies.
Arrays Core Summary
- Arrays store ordered collections of values.
- Array indexes begin at
0. - The
lengthproperty reports the array's size. - Use bracket notation or
at()to access elements. - Array contents can change even when the variable uses
const. - Use
Array.isArray()to identify arrays. - Spread syntax and
slice()create shallow copies. - Nested values remain shared in shallow copies.
- Arrays may contain objects, other arrays, and mixed data types.
JavaScript Array Mutation Methods
Mutation methods change the original array. They can add, remove, replace, reorder, or overwrite elements without creating a completely new array.
Understanding which methods mutate an array is important because multiple variables may reference the same array. A mutation performed through one reference becomes visible through every other reference to that array.
Mutating vs Non-Mutating Methods
| Category | Behavior | Examples |
|---|---|---|
| Mutating | Changes the original array. | push(), splice(), reverse() |
| Non-mutating | Returns a new array or value. | slice(), toSpliced(), toReversed() |
Adding Elements with push()
The push() method adds one or more elements to the end of an
array and returns the new array length.
const languages = [
"JavaScript",
"Python"
];
const newLength =
languages.push(
"SQL",
"PHP"
);
console.log(languages);
console.log(newLength);
["JavaScript", "Python", "SQL", "PHP"]
4
Removing the Last Element with pop()
The pop() method removes and returns the final element. Calling
it on an empty array returns undefined.
const languages = [
"JavaScript",
"Python",
"SQL"
];
const removed =
languages.pop();
console.log(removed);
console.log(languages);
SQL
["JavaScript", "Python"]
Adding Elements with unshift()
The unshift() method adds one or more elements to the beginning
of an array and returns the new length.
const queue = [
"Second",
"Third"
];
const newLength =
queue.unshift("First");
console.log(queue);
console.log(newLength);
["First", "Second", "Third"]
3
Removing the First Element with shift()
The shift() method removes and returns the first element. The
remaining elements move one index toward the beginning.
const queue = [
"First",
"Second",
"Third"
];
const removed =
queue.shift();
console.log(removed);
console.log(queue);
First
["Second", "Third"]
push(), pop(), shift(), and unshift()
| Method | Action | Return Value | Mutates? |
|---|---|---|---|
push() |
Add to the end. | New length | Yes |
pop() |
Remove from the end. | Removed element | Yes |
unshift() |
Add to the beginning. | New length | Yes |
shift() |
Remove from the beginning. | Removed element | Yes |
Adding or removing elements at the end is generally more efficient than
changing the beginning because shift() and
unshift() may require many indexes to be updated.
Removing Elements with splice()
The splice() method can remove, replace, or insert elements at
any position. It returns an array containing the removed elements.
const colors = [
"red",
"green",
"blue",
"yellow"
];
const removed =
colors.splice(1, 2);
console.log(colors);
console.log(removed);
["red", "yellow"]
["green", "blue"]
Inserting Elements with splice()
Use a delete count of 0 to insert values without removing
anything.
const languages = [
"JavaScript",
"SQL"
];
languages.splice(
1,
0,
"Python",
"TypeScript"
);
console.log(languages);
["JavaScript", "Python", "TypeScript", "SQL"]
Replacing Elements with splice()
const languages = [
"JavaScript",
"Python",
"SQL"
];
const removed =
languages.splice(
1,
1,
"TypeScript"
);
console.log(languages);
console.log(removed);
["JavaScript", "TypeScript", "SQL"]
["Python"]
splice() Syntax
| Argument | Purpose |
|---|---|
start |
The index where the modification begins. |
deleteCount |
The number of elements to remove. |
item1, item2, ... |
Optional values inserted at the starting index. |
Negative Indexes with splice()
A negative starting index counts backward from the end of the array.
const values = [
"A",
"B",
"C",
"D"
];
values.splice(
-2,
1,
"X"
);
console.log(values);
["A", "B", "X", "D"]
Non-Mutating Alternative: toSpliced()
The toSpliced() method performs an operation similar to
splice(), but returns a new array and leaves the original
unchanged.
const languages = [
"JavaScript",
"Python",
"SQL"
];
const updated =
languages.toSpliced(
1,
1,
"TypeScript"
);
console.log(languages);
console.log(updated);
["JavaScript", "Python", "SQL"]
["JavaScript", "TypeScript", "SQL"]
Reversing an Array with reverse()
The reverse() method reverses the element order in place and
returns the same array reference.
const numbers = [
1,
2,
3,
4
];
const reversed =
numbers.reverse();
console.log(numbers);
console.log(reversed);
console.log(
numbers === reversed
);
[4, 3, 2, 1]
[4, 3, 2, 1]
true
Non-Mutating Alternative: toReversed()
const numbers = [
1,
2,
3,
4
];
const reversed =
numbers.toReversed();
console.log(numbers);
console.log(reversed);
[1, 2, 3, 4]
[4, 3, 2, 1]
Filling an Array with fill()
The fill() method replaces elements with a specified value. It
changes the original array.
const values = [
1,
2,
3,
4,
5
];
values.fill(
0,
1,
4
);
console.log(values);
[1, 0, 0, 0, 5]
fill() Syntax
| Argument | Description |
|---|---|
value |
The value assigned to the selected positions. |
start |
Optional starting index; defaults to 0. |
end |
Optional exclusive ending index; defaults to the array length. |
Shared References with fill()
Filling an array with an object places the same object reference in every position.
const users =
new Array(3).fill({
active: false
});
users[0].active = true;
console.log(users);
[
{ active: true },
{ active: true },
{ active: true }
]
Every position references the same object. Use
Array.from() with a mapping function when each element needs
a separate object.
const users =
Array.from(
{ length: 3 },
() => ({
active: false
})
);
Copying Elements with copyWithin()
The copyWithin() method copies part of an array to another
position in the same array without changing its length.
const values = [
"A",
"B",
"C",
"D",
"E"
];
values.copyWithin(
0,
3
);
console.log(values);
["D", "E", "C", "D", "E"]
copyWithin() with a Limited Range
const values = [
1,
2,
3,
4,
5
];
values.copyWithin(
1,
3,
5
);
console.log(values);
[1, 4, 5, 4, 5]
Changing the length Property
The length property is writable. Reducing it permanently
removes elements from the end.
const values = [
"A",
"B",
"C",
"D"
];
values.length = 2;
console.log(values);
["A", "B"]
Increasing the length Property
Increasing length creates empty slots rather than explicit
undefined values.
const values = [
"A",
"B"
];
values.length = 5;
console.log(values);
console.log(values.length);
["A", "B", empty × 3]
5
Assigning a smaller length removes data permanently. Assigning a larger length creates a sparse array with empty positions.
Clearing an Array
Setting length to 0 clears the existing array
while preserving its reference.
const firstReference = [
1,
2,
3
];
const secondReference =
firstReference;
firstReference.length = 0;
console.log(firstReference);
console.log(secondReference);
[]
[]
Mutation Through Shared References
Assigning an array to another variable copies the reference, not the array itself.
const original = [
"JavaScript",
"Python"
];
const shared =
original;
shared.push("SQL");
console.log(original);
console.log(shared);
["JavaScript", "Python", "SQL"]
["JavaScript", "Python", "SQL"]
Create a copy before changing an array when the original must remain unchanged.
const copy = [
...original
];
copy.push("SQL");
Mutating and Non-Mutating Alternatives
| Mutating Method | Non-Mutating Alternative | Purpose |
|---|---|---|
push() |
[...array, value] |
Add to the end. |
unshift() |
[value, ...array] |
Add to the beginning. |
splice() |
toSpliced() |
Insert, remove, or replace. |
reverse() |
toReversed() |
Reverse element order. |
sort() |
toSorted() |
Sort elements. |
| Direct index assignment | array.with(index, value) |
Replace one element. |
Replacing an Element with with()
The with() method returns a new array with one element replaced
while leaving the original unchanged.
const languages = [
"JavaScript",
"Python",
"SQL"
];
const updated =
languages.with(
1,
"TypeScript"
);
console.log(languages);
console.log(updated);
["JavaScript", "Python", "SQL"]
["JavaScript", "TypeScript", "SQL"]
Mutation Methods Quick Reference
| Method | Purpose | Return Value |
|---|---|---|
push() |
Add elements to the end. | New length |
pop() |
Remove the final element. | Removed element |
unshift() |
Add elements to the beginning. | New length |
shift() |
Remove the first element. | Removed element |
splice() |
Insert, remove, or replace elements. | Array of removed values |
reverse() |
Reverse the original array. | The mutated array |
fill() |
Overwrite a selected range. | The mutated array |
copyWithin() |
Copy elements within the same array. | The mutated array |
Do not assume methods such as push() return the array.
push() and unshift() return the new length,
while pop() and shift() return the removed
element.
Use mutation when the array is intentionally owned and updated in one place. Prefer non-mutating alternatives when data is shared, when previous versions must be preserved, or when working with state-management systems that depend on new references.
Array Mutation Summary
push()andpop()modify the end of an array.unshift()andshift()modify the beginning.splice()inserts, removes, or replaces elements.reverse(),fill(), andcopyWithin()mutate the original array.toSpliced(),toReversed(), andwith()return new arrays.- Changing
lengthcan remove data or create empty slots. - Mutations are visible through every shared reference.
- Choose mutating or non-mutating methods intentionally.
JavaScript Array Iteration and Transformation Methods
JavaScript provides powerful array methods for visiting, transforming, filtering, searching, testing, and combining elements. These methods make collection processing more expressive than many traditional loops.
Most iteration methods receive a callback function. The callback is executed once for each relevant element and commonly receives the current value, its index, and the original array.
Callback Parameters
| Parameter | Description |
|---|---|
element |
The current array element. |
index |
The current zero-based index. |
array |
The array on which the method was called. |
const languages = [
"JavaScript",
"Python",
"SQL"
];
languages.forEach(
(language, index, array) => {
console.log(
language,
index,
array.length
);
}
);
JavaScript 0 3
Python 1 3
SQL 2 3
forEach()
The forEach() method executes a callback for each element. It
is useful for side effects such as logging, updating the DOM, or calling
another function.
const prices = [
10,
20,
30
];
prices.forEach(price => {
console.log(
`$${price}`
);
});
$10
$20
$30
The method always returns undefined. Use
map() when you need a transformed array.
map()
The map() method creates a new array containing the value
returned by the callback for each element.
const numbers = [
1,
2,
3,
4
];
const doubled =
numbers.map(number =>
number * 2
);
console.log(numbers);
console.log(doubled);
[1, 2, 3, 4]
[2, 4, 6, 8]
Transforming Objects with map()
const users = [
{
id: 1,
name: "Alice"
},
{
id: 2,
name: "Bob"
}
];
const names =
users.map(user =>
user.name
);
console.log(names);
["Alice", "Bob"]
Returning Objects from Arrow Functions
Wrap an object literal in parentheses when returning it implicitly from an arrow function.
const names = [
"Alice",
"Bob"
];
const users =
names.map(
(name, index) => ({
id: index + 1,
name
})
);
console.log(users);
[
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" }
]
filter()
The filter() method creates a new array containing only the
elements for which the callback returns a truthy value.
const numbers = [
1,
2,
3,
4,
5,
6
];
const evenNumbers =
numbers.filter(number =>
number % 2 === 0
);
console.log(evenNumbers);
[2, 4, 6]
Filtering Objects
const users = [
{
name: "Alice",
active: true
},
{
name: "Bob",
active: false
},
{
name: "Maya",
active: true
}
];
const activeUsers =
users.filter(user =>
user.active
);
console.log(activeUsers);
[
{ name: "Alice", active: true },
{ name: "Maya", active: true }
]
Removing Falsy Values with filter()
Passing Boolean removes all falsy values.
const values = [
"JavaScript",
"",
null,
"Python",
undefined,
0,
"SQL"
];
const cleaned =
values.filter(Boolean);
console.log(cleaned);
["JavaScript", "Python", "SQL"]
This pattern removes 0, false, and empty
strings. Use an explicit condition when those values are valid data.
find()
The find() method returns the first element that satisfies the
callback. It returns undefined when no match exists.
const users = [
{
id: 1,
name: "Alice"
},
{
id: 2,
name: "Bob"
}
];
const user =
users.find(item =>
item.id === 2
);
console.log(user);
{ id: 2, name: "Bob" }
findIndex()
The findIndex() method returns the index of the first matching
element, or -1 when no match exists.
const users = [
{
id: 1,
name: "Alice"
},
{
id: 2,
name: "Bob"
}
];
const index =
users.findIndex(user =>
user.id === 2
);
console.log(index);
1
findLast() and findLastIndex()
These methods search from the end of the array.
const numbers = [
3,
8,
4,
10,
6
];
console.log(
numbers.findLast(
number =>
number > 5
)
);
console.log(
numbers.findLastIndex(
number =>
number > 5
)
);
6
4
some()
The some() method returns true when at least one
element satisfies the callback.
const scores = [
42,
58,
91,
67
];
const hasHighScore =
scores.some(score =>
score >= 90
);
console.log(hasHighScore);
true
every()
The every() method returns true only when every
element satisfies the callback.
const ages = [
24,
31,
19,
42
];
const allAdults =
ages.every(age =>
age >= 18
);
console.log(allAdults);
true
Short-Circuit Behavior
Methods such as find(), some(), and
every() may stop early once the result is known.
| Method | Stops When |
|---|---|
find() |
The first matching element is found. |
findIndex() |
The first matching index is found. |
some() |
One callback returns truthy. |
every() |
One callback returns falsy. |
forEach() |
Does not normally stop early. |
reduce()
The reduce() method combines array elements into one final
value. The result may be a number, string, object, array, map, or any other
value.
const prices = [
10,
20,
30
];
const total =
prices.reduce(
(sum, price) =>
sum + price,
0
);
console.log(total);
60
reduce() Parameters
| Parameter | Description |
|---|---|
accumulator |
The value carried from one iteration to the next. |
currentValue |
The current array element. |
currentIndex |
The current index. |
array |
The original array. |
initialValue |
The starting accumulator value. |
Always Consider an Initial Value
Without an initial value, the first element becomes the initial accumulator.
Calling reduce() on an empty array without one throws a
TypeError.
const values = [];
const total =
values.reduce(
(sum, value) =>
sum + value,
0
);
console.log(total);
0
Counting Values with reduce()
const colors = [
"red",
"blue",
"red",
"green",
"blue",
"red"
];
const counts =
colors.reduce(
(result, color) => {
result[color] =
(result[color] ?? 0) + 1;
return result;
},
{}
);
console.log(counts);
{
red: 3,
blue: 2,
green: 1
}
Grouping Objects with reduce()
const products = [
{
name: "Laptop",
category: "Tech"
},
{
name: "Mouse",
category: "Tech"
},
{
name: "Chair",
category: "Furniture"
}
];
const grouped =
products.reduce(
(result, product) => {
const category =
product.category;
result[category] ??= [];
result[category].push(
product
);
return result;
},
{}
);
console.log(grouped);
{
Tech: [
{ name: "Laptop", category: "Tech" },
{ name: "Mouse", category: "Tech" }
],
Furniture: [
{ name: "Chair", category: "Furniture" }
]
}
reduceRight()
The reduceRight() method works like reduce() but
processes elements from right to left.
const words = [
"JavaScript",
"is",
"powerful"
];
const result =
words.reduceRight(
(sentence, word) =>
sentence
? `${sentence} ${word}`
: word,
""
);
console.log(result);
powerful is JavaScript
flat()
The flat() method creates a new array with nested arrays
flattened to a specified depth.
const values = [
1,
[2, 3],
[4, [5, 6]]
];
console.log(
values.flat()
);
console.log(
values.flat(2)
);
[1, 2, 3, 4, [5, 6]]
[1, 2, 3, 4, 5, 6]
Flattening Every Level
const deeplyNested = [
1,
[2, [3, [4]]]
];
const flatValues =
deeplyNested.flat(
Infinity
);
console.log(flatValues);
[1, 2, 3, 4]
flatMap()
The flatMap() method maps every element and then flattens the
result by one level.
const sentences = [
"Learn JavaScript",
"Build projects"
];
const words =
sentences.flatMap(
sentence =>
sentence.split(" ")
);
console.log(words);
["Learn", "JavaScript", "Build", "projects"]
Removing and Expanding with flatMap()
Returning an empty array removes an element. Returning multiple values expands one element into several.
const numbers = [
1,
2,
3,
4
];
const result =
numbers.flatMap(
number =>
number % 2 === 0
? [
number,
number * 10
]
: []
);
console.log(result);
[2, 20, 4, 40]
Method Chaining
Non-mutating array methods can be chained to build readable data-processing pipelines.
const products = [
{
name: "Laptop",
price: 1200,
active: true
},
{
name: "Mouse",
price: 40,
active: false
},
{
name: "Keyboard",
price: 80,
active: true
}
];
const total =
products
.filter(product =>
product.active
)
.map(product =>
product.price
)
.reduce(
(sum, price) =>
sum + price,
0
);
console.log(total);
1280
Choosing the Correct Method
| Goal | Recommended Method |
|---|---|
| Run code for every element | forEach() |
| Transform every element | map() |
| Keep matching elements | filter() |
| Find the first matching element | find() |
| Find the first matching position | findIndex() |
| Find the last matching element | findLast() |
| Check whether any element matches | some() |
| Check whether every element matches | every() |
| Combine all elements into one result | reduce() |
| Flatten nested arrays | flat() |
| Map and flatten one level | flatMap() |
Method Return Values
| Method | Returns | Mutates Original? |
|---|---|---|
forEach() |
undefined |
No, unless the callback mutates data. |
map() |
New array | No |
filter() |
New array | No |
find() |
Element or undefined |
No |
findIndex() |
Index or -1 |
No |
some() |
Boolean | No |
every() |
Boolean | No |
reduce() |
One accumulated value | No, unless the callback mutates data. |
flat() |
New array | No |
flatMap() |
New array | No |
Common Iteration Mistakes
- Using
forEach()when a new array is required. - Forgetting to return a value from a
map()callback. - Using
filter(Boolean)when0orfalseare valid values. - Forgetting that
find()returnsundefinedwhen no match exists. - Forgetting that
findIndex()returns-1when no match exists. - Calling
reduce()on an empty array without an initial value. - Using a complex
reduce()when simpler methods would be clearer. - Creating excessively long chains that are difficult to debug.
Choose methods based on the result you need: use
map() for transformations, filter() for
selection, find() for one matching element, and
reduce() only when values genuinely need to be accumulated
into one result.
Array Iteration Summary
forEach()runs a callback for every element.map()transforms every element into a new array.filter()keeps only matching elements.find()andfindIndex()return the first match.findLast()andfindLastIndex()search from the end.some()checks whether at least one element matches.every()checks whether all elements match.reduce()combines the array into one result.flat()removes nested array levels.flatMap()maps and flattens one level.- Method chaining can create readable data-processing pipelines.
JavaScript Array Search, Sort and Advanced Patterns
JavaScript arrays include methods for searching, sorting, comparing, deduplicating, destructuring, and processing multidimensional data. These techniques are essential when working with lists, records, search results, tables, dashboards, and API responses.
This final arrays block completes the chapter with practical search methods, sorting rules, modern non-mutating alternatives, common patterns, best practices, and a final quick reference.
Searching with includes()
The includes() method checks whether an array contains a
specific value. It returns a Boolean.
const languages = [
"JavaScript",
"Python",
"SQL"
];
console.log(
languages.includes(
"Python"
)
);
console.log(
languages.includes(
"PHP"
)
);
true
false
Searching with indexOf()
The indexOf() method returns the first matching index, or
-1 when the value is not found.
const colors = [
"red",
"green",
"blue",
"green"
];
console.log(
colors.indexOf(
"green"
)
);
console.log(
colors.indexOf(
"yellow"
)
);
1
-1
Searching from the End with lastIndexOf()
const colors = [
"red",
"green",
"blue",
"green"
];
console.log(
colors.lastIndexOf(
"green"
)
);
3
includes() vs indexOf()
| Method | Returns | Best Used For |
|---|---|---|
includes() |
Boolean | Checking whether a value exists. |
indexOf() |
Index or -1 |
Finding the first position. |
lastIndexOf() |
Index or -1 |
Finding the final position. |
find() |
Element or undefined |
Searching with a condition. |
findIndex() |
Index or -1 |
Finding a position with a condition. |
includes(NaN) can find NaN, while
indexOf(NaN) returns -1.
const values = [
10,
NaN,
20
];
console.log(
values.includes(NaN)
);
console.log(
values.indexOf(NaN)
);
true
-1
Sorting Strings with sort()
The sort() method changes the original array and sorts values
as strings by default.
const languages = [
"Python",
"JavaScript",
"CSS",
"HTML"
];
languages.sort();
console.log(languages);
["CSS", "HTML", "JavaScript", "Python"]
Default Numeric Sorting Problem
Without a comparison function, numbers are converted to strings before sorting.
const numbers = [
2,
100,
15,
8
];
numbers.sort();
console.log(numbers);
[100, 15, 2, 8]
The values are compared as text, so "100" comes before
"15" and "2".
Sorting Numbers Correctly
const numbers = [
2,
100,
15,
8
];
numbers.sort(
(first, second) =>
first - second
);
console.log(numbers);
[2, 8, 15, 100]
Ascending and Descending Sort
| Order | Comparison Function |
|---|---|
| Ascending | (a, b) => a - b |
| Descending | (a, b) => b - a |
const numbers = [
2,
100,
15,
8
];
numbers.sort(
(first, second) =>
second - first
);
console.log(numbers);
[100, 15, 8, 2]
How the Comparison Function Works
| Returned Value | Meaning |
|---|---|
| Negative number | Place the first value before the second. |
0 |
Keep their relative ordering unchanged. |
| Positive number | Place the first value after the second. |
Sorting Objects
A comparison function may access object properties.
const products = [
{
name: "Keyboard",
price: 80
},
{
name: "Laptop",
price: 1200
},
{
name: "Mouse",
price: 40
}
];
products.sort(
(first, second) =>
first.price -
second.price
);
console.log(products);
[
{ name: "Mouse", price: 40 },
{ name: "Keyboard", price: 80 },
{ name: "Laptop", price: 1200 }
]
Sorting Text Properties
Use localeCompare() for readable text sorting.
const users = [
{
name: "Maya"
},
{
name: "Alice"
},
{
name: "Bob"
}
];
users.sort(
(first, second) =>
first.name.localeCompare(
second.name
)
);
console.log(users);
[
{ name: "Alice" },
{ name: "Bob" },
{ name: "Maya" }
]
Non-Mutating Sorting with toSorted()
The toSorted() method returns a sorted copy and preserves the
original array.
const numbers = [
20,
5,
100,
12
];
const sorted =
numbers.toSorted(
(first, second) =>
first - second
);
console.log(numbers);
console.log(sorted);
[20, 5, 100, 12]
[5, 12, 20, 100]
Older Non-Mutating Sort Pattern
Spread syntax followed by sort() also preserves the original
outer array.
const numbers = [
20,
5,
100,
12
];
const sorted = [
...numbers
].sort(
(first, second) =>
first - second
);
console.log(numbers);
console.log(sorted);
[20, 5, 100, 12]
[5, 12, 20, 100]
Sorting by Multiple Properties
Combine comparisons to create primary and secondary sort rules.
const users = [
{
name: "Maya",
age: 30
},
{
name: "Alice",
age: 25
},
{
name: "Bob",
age: 25
}
];
const sorted =
users.toSorted(
(first, second) =>
first.age -
second.age ||
first.name.localeCompare(
second.name
)
);
console.log(sorted);
[
{ name: "Alice", age: 25 },
{ name: "Bob", age: 25 },
{ name: "Maya", age: 30 }
]
Array Destructuring
Destructuring extracts array elements into variables according to their position.
const languages = [
"JavaScript",
"Python",
"SQL"
];
const [
first,
second,
third
] = languages;
console.log(first);
console.log(second);
console.log(third);
JavaScript
Python
SQL
Skipping Elements
const values = [
"A",
"B",
"C"
];
const [
first,
,
third
] = values;
console.log(first);
console.log(third);
A
C
Default Values in Destructuring
const values = [
"JavaScript"
];
const [
language,
level = "Beginner"
] = values;
console.log(language);
console.log(level);
JavaScript
Beginner
Rest Elements in Destructuring
const values = [
10,
20,
30,
40
];
const [
first,
...remaining
] = values;
console.log(first);
console.log(remaining);
10
[20, 30, 40]
Swapping Variables
let first = "A";
let second = "B";
[
first,
second
] = [
second,
first
];
console.log(first);
console.log(second);
B
A
Multidimensional Arrays
Nested arrays can represent grids, matrices, tables, game boards, and grouped data.
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
console.log(
matrix[1][2]
);
6
Looping Through Nested Arrays
const matrix = [
[1, 2],
[3, 4],
[5, 6]
];
for (const row of matrix) {
for (const value of row) {
console.log(value);
}
}
1
2
3
4
5
6
Removing Duplicate Primitive Values
Convert the array to a Set and spread the unique values back
into a new array.
const values = [
"JavaScript",
"Python",
"JavaScript",
"SQL",
"Python"
];
const uniqueValues = [
...new Set(values)
];
console.log(uniqueValues);
["JavaScript", "Python", "SQL"]
Removing Duplicate Objects by Property
Objects are compared by reference, so deduplicating records requires a specific key or custom strategy.
const users = [
{
id: 1,
name: "Alice"
},
{
id: 2,
name: "Bob"
},
{
id: 1,
name: "Alice"
}
];
const uniqueUsers = [
...new Map(
users.map(user => [
user.id,
user
])
).values()
];
console.log(uniqueUsers);
[
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" }
]
Creating an Index by ID
A Map provides fast lookup when records have unique keys.
const users = [
{
id: 1,
name: "Alice"
},
{
id: 2,
name: "Bob"
}
];
const usersById =
new Map(
users.map(user => [
user.id,
user
])
);
console.log(
usersById.get(2)
);
{ id: 2, name: "Bob" }
Finding Minimum and Maximum Values
const numbers = [
12,
5,
87,
24
];
console.log(
Math.min(...numbers)
);
console.log(
Math.max(...numbers)
);
5
87
Spreading an extremely large array into a function call may exceed the
engine's argument limit. Use a loop or reduce() for very
large collections.
Calculating an Average
const scores = [
80,
90,
70,
100
];
const total =
scores.reduce(
(sum, score) =>
sum + score,
0
);
const average =
scores.length
? total / scores.length
: 0;
console.log(average);
85
Randomizing an Array
A Fisher–Yates shuffle provides an unbiased in-place random ordering.
function shuffle(values) {
const result = [
...values
];
for (
let index =
result.length - 1;
index > 0;
index--
) {
const randomIndex =
Math.floor(
Math.random() *
(index + 1)
);
[
result[index],
result[randomIndex]
] = [
result[randomIndex],
result[index]
];
}
return result;
}
console.log(
shuffle([1, 2, 3, 4, 5])
);
That shortcut does not produce a reliably uniform shuffle. Use the Fisher–Yates algorithm when fair randomization matters.
Array Equality
Arrays are reference values. Two separate arrays are not strictly equal, even when they contain identical elements.
const first = [
1,
2,
3
];
const second = [
1,
2,
3
];
const shared = first;
console.log(
first === second
);
console.log(
first === shared
);
false
true
Comparing Primitive Arrays by Value
function arraysEqual(
first,
second
) {
return (
first.length ===
second.length &&
first.every(
(value, index) =>
Object.is(
value,
second[index]
)
)
);
}
console.log(
arraysEqual(
[1, 2, 3],
[1, 2, 3]
)
);
console.log(
arraysEqual(
[1, 2, 3],
[3, 2, 1]
)
);
true
false
This helper performs a shallow positional comparison. Nested objects and arrays require deeper comparison rules.
Common Array Mistakes
| Mistake | Why It Causes Problems | Better Approach |
|---|---|---|
| Forgetting zero-based indexing | Reads or updates the wrong element. | Remember that the first index is 0. |
Using typeof to identify arrays |
It returns "object". |
Use Array.isArray(). |
| Sorting numbers without a comparator | Numbers are sorted as strings. | Use (a, b) => a - b. |
Forgetting that sort() mutates |
The original ordering is lost. | Use toSorted() or copy first. |
| Assuming copied nested data is independent | Shallow copies retain nested references. | Use an appropriate deep-copy strategy. |
Comparing arrays with === |
Strict equality compares references. | Compare the required values explicitly. |
| Creating sparse arrays accidentally | Empty slots behave inconsistently across methods. | Add elements sequentially or use array methods. |
Using map() only for side effects |
Creates an unused array. | Use forEach(). |
Using forEach() when early exit is needed |
It does not support normal break. |
Use a loop, find(), some(), or every(). |
Using a complex reduce() unnecessarily |
Makes the logic harder to understand. | Prefer clearer specialized methods when possible. |
Array Best Practices
- Use array literals instead of the constructor for ordinary arrays.
- Use
constwhen the array variable will not be reassigned. - Use
Array.isArray()for reliable array checks. - Prefer
map(),filter(), andfind()when they clearly express the task. - Use
toSorted(),toReversed(),toSpliced(), andwith()when the original must remain unchanged. - Provide an initial value to
reduce()when appropriate. - Use a numeric comparison function when sorting numbers.
- Use
localeCompare()for user-facing text sorting. - Avoid unnecessary sparse arrays.
- Keep method chains short and readable.
- Document whether helper functions mutate their inputs.
- Use
MaporSetwhen keyed lookup or uniqueness is the real requirement.
Complete Array Methods Quick Reference
| Goal | Recommended Method | Mutates? |
|---|---|---|
| Add to the end | push() |
Yes |
| Remove from the end | pop() |
Yes |
| Add to the beginning | unshift() |
Yes |
| Remove from the beginning | shift() |
Yes |
| Insert, remove, or replace | splice() |
Yes |
| Insert, remove, or replace immutably | toSpliced() |
No |
| Transform every element | map() |
No |
| Keep matching elements | filter() |
No |
| Find one element | find() |
No |
| Find one index | findIndex() |
No |
| Check whether a value exists | includes() |
No |
| Find an exact value's index | indexOf() |
No |
| Check whether any element matches | some() |
No |
| Check whether all elements match | every() |
No |
| Combine into one result | reduce() |
No |
| Flatten nested arrays | flat() |
No |
| Map and flatten one level | flatMap() |
No |
| Sort the original | sort() |
Yes |
| Create a sorted copy | toSorted() |
No |
| Reverse the original | reverse() |
Yes |
| Create a reversed copy | toReversed() |
No |
| Replace one element immutably | with() |
No |
| Create a shallow copy | [...array] or slice() |
No |
| Remove duplicate primitives | [...new Set(array)] |
No |
Methods such as sort(), reverse(),
splice(), fill(), and
copyWithin() change the original array. Always verify
whether callers expect the input to remain unchanged.
JavaScript Arrays Chapter Complete
- Arrays store ordered values with zero-based indexes.
- Arrays can contain primitives, objects, functions, and nested arrays.
- Mutation methods change the original array.
- Modern copy methods provide non-mutating alternatives.
- Iteration methods simplify transformation, filtering, searching, and aggregation.
- Numeric sorting requires a comparison function.
- Destructuring extracts values by position.
Setremoves duplicate primitive values.Mapsupports keyed lookup and object deduplication.- Arrays compare by reference rather than content.
- Shallow copies do not clone nested objects.
- Choose each method according to its return value and mutation behavior.
JavaScript Objects
JavaScript objects store related data as key-value pairs called properties. Objects are used to represent users, products, settings, API responses, application state, configuration, and almost every other structured value in JavaScript.
Unlike primitive values, objects are mutable reference types. Their
properties can be added, updated, or removed after creation, even when
the object variable was declared with const.
Creating an Object
Object literal syntax is the recommended way to create most JavaScript objects.
const user = {
name: "Alice",
age: 30,
active: true
};
console.log(user);
{
name: "Alice",
age: 30,
active: true
}
Object Properties
Each property contains a key and a value. Property values may use any JavaScript data type, including arrays, functions, and other objects.
| Property | Value | Value Type |
|---|---|---|
name |
"Alice" |
String |
age |
30 |
Number |
active |
true |
Boolean |
skills |
["JavaScript", "SQL"] |
Array |
address |
{ city: "London" } |
Object |
greet |
function () {} |
Function |
Dot Notation
Dot notation is concise and readable when the property name is known and is a valid JavaScript identifier.
const user = {
name: "Alice",
age: 30
};
console.log(user.name);
console.log(user.age);
Alice
30
Bracket Notation
Bracket notation accepts a string or expression and is required for dynamic property names or keys containing spaces and special characters.
const user = {
name: "Alice",
"account status": "active"
};
console.log(
user["name"]
);
console.log(
user["account status"]
);
Alice
active
Dot Notation vs Bracket Notation
| Situation | Dot Notation | Bracket Notation |
|---|---|---|
| Known identifier-style property | Recommended | Valid |
| Dynamic property name | Not supported | Required |
| Property containing spaces | Not supported | Required |
| Property containing a hyphen | Not supported | Required |
| Numeric property key | Usually unsuitable | Recommended |
Dynamic Property Access
A variable can provide the key used by bracket notation.
const user = {
name: "Alice",
age: 30
};
const propertyName =
"name";
console.log(
user[propertyName]
);
Alice
const propertyName = "name";
console.log(
user.propertyName
);
This searches for a literal property named propertyName.
Use user[propertyName] when the variable contains the key.
Adding Properties
Assign a value to a new property name to add it to the object.
const user = {
name: "Alice"
};
user.age = 30;
user["account status"] =
"active";
console.log(user);
{
name: "Alice",
age: 30,
"account status": "active"
}
Updating Properties
Assigning a value to an existing property replaces its current value.
const user = {
name: "Alice",
age: 30
};
user.name = "Maya";
user.age = 31;
console.log(user);
{
name: "Maya",
age: 31
}
Deleting Properties
The delete operator removes a property from an object and
normally returns true.
const user = {
name: "Alice",
age: 30,
password: "secret"
};
const deleted =
delete user.password;
console.log(deleted);
console.log(user);
true
{
name: "Alice",
age: 30
}
Missing Properties
Reading a property that does not exist returns undefined.
const user = {
name: "Alice"
};
console.log(
user.email
);
undefined
Checking Whether a Property Exists
| Technique | Checks Own Properties? | Checks Prototype Chain? |
|---|---|---|
Object.hasOwn(object, key) |
Yes | No |
key in object |
Yes | Yes |
object[key] !== undefined |
Not reliably | Depends on lookup |
const user = {
name: "Alice",
email: undefined
};
console.log(
Object.hasOwn(
user,
"name"
)
);
console.log(
Object.hasOwn(
user,
"email"
)
);
console.log(
Object.hasOwn(
user,
"age"
)
);
true
true
false
A property may exist while its value is undefined.
Object.hasOwn() checks property ownership instead of the
stored value.
The in Operator
The in operator checks both the object and its prototype chain.
const user = {
name: "Alice"
};
console.log(
"name" in user
);
console.log(
"toString" in user
);
console.log(
"email" in user
);
true
true
false
Computed Property Names
Square brackets inside an object literal allow an expression to determine the property key.
const fieldName =
"email";
const user = {
name: "Alice",
[fieldName]:
"alice@example.com"
};
console.log(user);
{
name: "Alice",
email: "alice@example.com"
}
Property Shorthand
When a variable name matches the desired property key, include the variable without repeating its name.
const name = "Alice";
const age = 30;
const active = true;
const user = {
name,
age,
active
};
console.log(user);
{
name: "Alice",
age: 30,
active: true
}
Methods Inside Objects
A function stored as an object property is called a method.
const user = {
name: "Alice",
greet: function () {
return "Hello!";
}
};
console.log(
user.greet()
);
Hello!
Method Shorthand
Modern object literals support a shorter syntax for declaring methods.
const user = {
name: "Alice",
greet() {
return `Hello ${this.name}`;
}
};
console.log(
user.greet()
);
Hello Alice
The this Keyword in Methods
In a normal method call such as user.greet(),
this usually refers to the object before the dot.
const product = {
name: "Keyboard",
price: 80,
quantity: 2,
getTotal() {
return (
this.price *
this.quantity
);
}
};
console.log(
product.getTotal()
);
160
const user = {
name: "Alice",
greet: () => {
return this.name;
}
};
Arrow functions do not receive their own this. Use method
shorthand or a regular function when a method must access the object
through this.
Nested Objects
Objects may contain other objects to represent hierarchical data.
const user = {
name: "Alice",
address: {
city: "London",
country: "UK"
}
};
console.log(
user.address.city
);
console.log(
user["address"]["country"]
);
London
UK
Objects Containing Arrays
const course = {
title:
"JavaScript Fundamentals",
topics: [
"Variables",
"Arrays",
"Objects"
]
};
console.log(
course.topics[1]
);
Arrays
Optional Chaining
The optional chaining operator ?. safely accesses a property
or method when an intermediate value may be null or
undefined.
const user = {
name: "Alice"
};
console.log(
user.address?.city
);
console.log(
user.profile?.settings?.theme
);
undefined
undefined
Optional Method Calls
const user = {
name: "Alice"
};
const result =
user.greet?.();
console.log(result);
undefined
Optional chaining prevents an error when a value is missing. It does not guarantee that the object has the correct structure or data types.
Optional Chaining with Nullish Coalescing
Combine ?. with ?? to provide a fallback only
when the accessed value is null or undefined.
const user = {
name: "Alice",
settings: {
theme: null
}
};
const theme =
user.settings?.theme ??
"system";
console.log(theme);
system
Why const Objects Can Change
const prevents reassignment of the variable. It does not make
the object or its properties immutable.
const user = {
name: "Alice"
};
user.name = "Maya";
user.active = true;
console.log(user);
{
name: "Maya",
active: true
}
const user = {
name: "Alice"
};
// TypeError
user = {
name: "Maya"
};
Objects Are Reference Types
Assigning an object to another variable copies its reference rather than creating a new object.
const firstUser = {
name: "Alice"
};
const secondUser =
firstUser;
secondUser.name = "Maya";
console.log(
firstUser.name
);
console.log(
secondUser.name
);
Maya
Maya
Object Equality
Strict equality compares object references, not their property contents.
const first = {
name: "Alice"
};
const second = {
name: "Alice"
};
const shared = first;
console.log(
first === second
);
console.log(
first === shared
);
false
true
Basic Object Syntax Reference
| Syntax | Purpose |
|---|---|
{ key: value } |
Create an object literal. |
object.key |
Access a known property. |
object[key] |
Access a dynamic property. |
object.key = value |
Add or update a property. |
delete object.key |
Remove a property. |
Object.hasOwn(object, key) |
Check for an own property. |
key in object |
Check the object and its prototype chain. |
object?.key |
Safely access a potentially missing nested value. |
object.method() |
Call a method stored on an object. |
[computedKey]: value |
Create a computed property name. |
Use dot notation for known property names, bracket notation for dynamic
keys, Object.hasOwn() for ownership checks, and optional
chaining only when missing nested data is expected.
Objects Core Summary
- Objects store data as key-value properties.
- Object literal syntax is recommended for most objects.
- Dot notation accesses known identifier-style keys.
- Bracket notation supports dynamic and unusual keys.
- Properties can be added, updated, or deleted.
Object.hasOwn()checks for own properties.- Functions stored on objects are methods.
- Normal methods can access the object with
this. - Optional chaining safely handles missing nested values.
constprevents reassignment but not property mutation.- Objects are shared and compared by reference.
JavaScript Object Keys, Values and Entries
JavaScript provides built-in methods for reading an object's property names, values, and key-value pairs. These methods convert object data into arrays, making it easier to search, filter, transform, sort, and iterate over object properties.
The most important utilities are Object.keys(),
Object.values(), Object.entries(),
Object.fromEntries(), and
Object.hasOwn().
Object.keys()
The Object.keys() method returns an array containing the
object's own enumerable string-keyed property names.
const user = {
name: "Alice",
age: 30,
active: true
};
const keys =
Object.keys(user);
console.log(keys);
["name", "age", "active"]
Counting Object Properties
Objects do not have a normal length property. Use the length
of the array returned by Object.keys().
const settings = {
theme: "dark",
language: "en",
notifications: true
};
const propertyCount =
Object.keys(settings).length;
console.log(propertyCount);
3
Iterating Over Object Keys
Because Object.keys() returns an array, it can be used with
for...of, forEach(), and other array methods.
const user = {
name: "Alice",
age: 30,
active: true
};
for (
const key of
Object.keys(user)
) {
console.log(
key,
user[key]
);
}
name Alice
age 30
active true
Filtering Object Keys
const product = {
name: "Laptop",
price: 1200,
stock: 5,
active: true
};
const numericKeys =
Object.keys(product)
.filter(key =>
typeof product[key] ===
"number"
);
console.log(numericKeys);
["price", "stock"]
Object.values()
The Object.values() method returns an array containing the
object's own enumerable string-keyed property values.
const user = {
name: "Alice",
age: 30,
active: true
};
const values =
Object.values(user);
console.log(values);
["Alice", 30, true]
Calculating a Total with Object.values()
const monthlySales = {
january: 1200,
february: 1500,
march: 1800
};
const total =
Object.values(
monthlySales
).reduce(
(sum, value) =>
sum + value,
0
);
console.log(total);
4500
Testing Object Values
Array methods such as some() and every() can test
the values returned by Object.values().
const permissions = {
read: true,
write: false,
delete: false
};
const hasPermission =
Object.values(
permissions
).some(Boolean);
const hasAllPermissions =
Object.values(
permissions
).every(Boolean);
console.log(hasPermission);
console.log(
hasAllPermissions
);
true
false
Object.entries()
The Object.entries() method returns an array of
[key, value] pairs.
const user = {
name: "Alice",
age: 30,
active: true
};
const entries =
Object.entries(user);
console.log(entries);
[
["name", "Alice"],
["age", 30],
["active", true]
]
Iterating with Object.entries()
Destructure each entry into a key and value while iterating.
const product = {
name: "Keyboard",
price: 80,
stock: 12
};
for (
const [key, value] of
Object.entries(product)
) {
console.log(
`${key}: ${value}`
);
}
name: Keyboard
price: 80
stock: 12
Filtering Object Entries
Convert the object to entries, filter the pairs, and convert the result back
into an object with Object.fromEntries().
const user = {
name: "Alice",
age: 30,
password: "secret",
active: true
};
const publicUser =
Object.fromEntries(
Object.entries(user)
.filter(
([key]) =>
key !== "password"
)
);
console.log(publicUser);
{
name: "Alice",
age: 30,
active: true
}
Transforming Object Values
Use map() on the entries to transform the values while
preserving the keys.
const prices = {
laptop: 1200,
keyboard: 80,
mouse: 40
};
const discountedPrices =
Object.fromEntries(
Object.entries(prices)
.map(
([key, value]) => [
key,
value * 0.9
]
)
);
console.log(
discountedPrices
);
{
laptop: 1080,
keyboard: 72,
mouse: 36
}
Transforming Object Keys
const settings = {
darkMode: true,
emailAlerts: false
};
const uppercaseKeys =
Object.fromEntries(
Object.entries(settings)
.map(
([key, value]) => [
key.toUpperCase(),
value
]
)
);
console.log(
uppercaseKeys
);
{
DARKMODE: true,
EMAILALERTS: false
}
Object.fromEntries()
The Object.fromEntries() method converts an iterable of
key-value pairs into an object.
const entries = [
["name", "Alice"],
["age", 30],
["active", true]
];
const user =
Object.fromEntries(
entries
);
console.log(user);
{
name: "Alice",
age: 30,
active: true
}
Creating an Object from a Map
A Map is iterable as key-value pairs and can be passed directly
to Object.fromEntries().
const settingsMap =
new Map([
["theme", "dark"],
["language", "en"],
["notifications", true]
]);
const settings =
Object.fromEntries(
settingsMap
);
console.log(settings);
{
theme: "dark",
language: "en",
notifications: true
}
Creating an Object from URL Parameters
URLSearchParams produces iterable key-value pairs that can be
converted into an object.
const parameters =
new URLSearchParams(
"page=2&sort=price&order=asc"
);
const query =
Object.fromEntries(
parameters
);
console.log(query);
{
page: "2",
sort: "price",
order: "asc"
}
When several entries use the same key, the final value overwrites the earlier values.
const object =
Object.fromEntries([
["status", "pending"],
["status", "complete"]
]);
console.log(object.status);
// "complete"
Object.hasOwn()
The Object.hasOwn() method returns true when the
object directly owns the specified property.
const user = {
name: "Alice",
email: undefined
};
console.log(
Object.hasOwn(
user,
"name"
)
);
console.log(
Object.hasOwn(
user,
"email"
)
);
console.log(
Object.hasOwn(
user,
"toString"
)
);
true
true
false
Object.hasOwn() vs the in Operator
| Technique | Own Properties | Inherited Properties |
|---|---|---|
Object.hasOwn(object, key) |
Yes | No |
key in object |
Yes | Yes |
object[key] !== undefined |
Unreliable | May include inherited values |
const user = {
name: "Alice"
};
console.log(
Object.hasOwn(
user,
"toString"
)
);
console.log(
"toString" in user
);
false
true
Why Not Use hasOwnProperty() Directly?
Older code frequently calls object.hasOwnProperty(). Modern
code should generally prefer Object.hasOwn().
| Issue | Explanation |
|---|---|
| Missing prototype | An object created with Object.create(null) has no inherited method. |
| Overridden property | The object may define its own property named hasOwnProperty. |
| Clearer intent | Object.hasOwn() explicitly receives the object and key. |
const data =
Object.create(null);
data.name = "Alice";
console.log(
Object.hasOwn(
data,
"name"
)
);
true
Enumerable Own Properties
Object.keys(), Object.values(), and
Object.entries() include only own enumerable string-keyed
properties.
const user = {
name: "Alice"
};
Object.defineProperty(
user,
"id",
{
value: 123,
enumerable: false
}
);
console.log(
Object.keys(user)
);
console.log(user.id);
["name"]
123
Symbol Properties Are Excluded
The standard keys, values, and entries methods do not include symbol-keyed properties.
const identifier =
Symbol("id");
const user = {
name: "Alice",
[identifier]: 123
};
console.log(
Object.keys(user)
);
console.log(
user[identifier]
);
["name"]
123
Object.getOwnPropertyNames()
This method returns own string-keyed property names, including non-enumerable properties.
const user = {
name: "Alice"
};
Object.defineProperty(
user,
"id",
{
value: 123,
enumerable: false
}
);
console.log(
Object.getOwnPropertyNames(
user
)
);
["name", "id"]
Object.getOwnPropertySymbols()
Use this method to retrieve an object's own symbol-keyed properties.
const identifier =
Symbol("id");
const user = {
name: "Alice",
[identifier]: 123
};
const symbols =
Object.getOwnPropertySymbols(
user
);
console.log(symbols);
console.log(
user[symbols[0]]
);
[Symbol(id)]
123
Reflect.ownKeys()
The Reflect.ownKeys() method returns all own property keys,
including strings, symbols, enumerable properties, and non-enumerable
properties.
const identifier =
Symbol("id");
const user = {
name: "Alice",
[identifier]: 123
};
Object.defineProperty(
user,
"secret",
{
value: true,
enumerable: false
}
);
console.log(
Reflect.ownKeys(user)
);
[
"name",
"secret",
Symbol(id)
]
Property Retrieval Comparison
| Method | Enumerable Only? | String Keys? | Symbol Keys? |
|---|---|---|---|
Object.keys() |
Yes | Yes | No |
Object.values() |
Yes | Returns corresponding values | No |
Object.entries() |
Yes | Yes | No |
Object.getOwnPropertyNames() |
No | Yes | No |
Object.getOwnPropertySymbols() |
No | No | Yes |
Reflect.ownKeys() |
No | Yes | Yes |
Common Property Utility Mistakes
- Expecting objects to have a built-in
lengthproperty. - Assuming
Object.keys()includes inherited properties. - Assuming
Object.keys()includes symbol properties. - Checking property existence with
object[key] !== undefined. - Calling
hasOwnProperty()directly on an unknown object. - Forgetting that duplicate entries overwrite earlier keys.
- Assuming URL parameter values are automatically converted to numbers or Booleans.
Use Object.keys() when you need property names,
Object.values() when only the values matter, and
Object.entries() when both are required. Use
Object.fromEntries() to rebuild an object after filtering or
transforming its entries.
Object Property Utilities Summary
Object.keys()returns enumerable own string keys.Object.values()returns enumerable own property values.Object.entries()returns key-value pair arrays.Object.fromEntries()converts pairs into an object.- Entries can be filtered and transformed with array methods.
Object.hasOwn()reliably checks own-property existence.- The
inoperator also checks inherited properties. - Non-enumerable and symbol properties require different retrieval methods.
Reflect.ownKeys()returns every own property key.
JavaScript Object Copying, Merging and Updating
JavaScript provides several ways to copy, merge, and update objects.
The most common techniques are object spread syntax and
Object.assign().
These techniques create shallow copies. The new outer object is separate, but nested objects and arrays remain shared unless they are copied explicitly.
Copying an Object with Spread Syntax
Object spread syntax creates a new object containing the source object's own enumerable properties.
const original = {
name: "Alice",
age: 30
};
const copy = {
...original
};
copy.name = "Maya";
console.log(original);
console.log(copy);
{
name: "Alice",
age: 30
}
{
name: "Maya",
age: 30
}
Copying with Object.assign()
Object.assign() copies own enumerable properties from one or
more source objects into a target object.
const original = {
name: "Alice",
age: 30
};
const copy =
Object.assign(
{},
original
);
copy.age = 31;
console.log(original);
console.log(copy);
{
name: "Alice",
age: 30
}
{
name: "Alice",
age: 31
}
Spread Syntax vs Object.assign()
| Feature | Object Spread | Object.assign() |
|---|---|---|
| Creates a new object | Yes, with { ...source } |
Yes, when target is {} |
| Can mutate an existing target | No | Yes |
| Copies own enumerable properties | Yes | Yes |
| Creates a shallow copy | Yes | Yes |
| Common modern choice | Yes | Useful for explicit target assignment |
Updating Properties Immutably
Place the original object first and the updated properties afterward. Later properties overwrite earlier properties with the same key.
const user = {
id: 1,
name: "Alice",
active: false
};
const updatedUser = {
...user,
active: true
};
console.log(user);
console.log(updatedUser);
{
id: 1,
name: "Alice",
active: false
}
{
id: 1,
name: "Alice",
active: true
}
const wrongOrder = {
active: true,
...user
};
If user.active is false, the spread operation
overwrites the earlier true value.
Adding Properties Immutably
const user = {
name: "Alice",
age: 30
};
const updatedUser = {
...user,
role: "admin"
};
console.log(updatedUser);
{
name: "Alice",
age: 30,
role: "admin"
}
Removing a Property Immutably
Use object destructuring with a rest property to exclude a key while creating a new object.
const user = {
name: "Alice",
age: 30,
password: "secret"
};
const {
password,
...publicUser
} = user;
console.log(publicUser);
console.log(password);
{
name: "Alice",
age: 30
}
secret
Removing a Dynamic Property
Computed property destructuring can remove a key stored in a variable.
const user = {
name: "Alice",
age: 30,
password: "secret"
};
const keyToRemove =
"password";
const {
[keyToRemove]: removed,
...remaining
} = user;
console.log(remaining);
console.log(removed);
{
name: "Alice",
age: 30
}
secret
Merging Objects with Spread Syntax
Spread multiple objects into a new object. When keys conflict, the last value wins.
const defaults = {
theme: "light",
language: "en",
notifications: true
};
const preferences = {
theme: "dark",
notifications: false
};
const settings = {
...defaults,
...preferences
};
console.log(settings);
{
theme: "dark",
language: "en",
notifications: false
}
Merging with Object.assign()
const defaults = {
theme: "light",
language: "en"
};
const preferences = {
theme: "dark"
};
const settings =
Object.assign(
{},
defaults,
preferences
);
console.log(settings);
{
theme: "dark",
language: "en"
}
Object.assign() Can Mutate the Target
When an existing object is used as the first argument, it becomes the mutation target.
const settings = {
theme: "light"
};
const result =
Object.assign(
settings,
{
theme: "dark",
language: "en"
}
);
console.log(settings);
console.log(
settings === result
);
{
theme: "dark",
language: "en"
}
true
Use an empty object as the target when you need a new object:
Object.assign({}, source).
Shallow Copy Behavior
Spread syntax and Object.assign() copy only the first property
level. Nested reference values remain shared.
const original = {
name: "Alice",
address: {
city: "London",
country: "UK"
}
};
const copy = {
...original
};
copy.address.city =
"Manchester";
console.log(
original.address.city
);
console.log(
copy.address.city
);
Manchester
Manchester
The outer objects are different, but both address properties
point to the same nested object.
Copying a Nested Object Manually
Copy every nested level that must become independent.
const original = {
name: "Alice",
address: {
city: "London",
country: "UK"
}
};
const copy = {
...original,
address: {
...original.address
}
};
copy.address.city =
"Manchester";
console.log(
original.address.city
);
console.log(
copy.address.city
);
London
Manchester
Updating a Nested Property Immutably
const user = {
name: "Alice",
settings: {
theme: "light",
notifications: true
}
};
const updatedUser = {
...user,
settings: {
...user.settings,
theme: "dark"
}
};
console.log(user);
console.log(updatedUser);
{
name: "Alice",
settings: {
theme: "light",
notifications: true
}
}
{
name: "Alice",
settings: {
theme: "dark",
notifications: true
}
}
Updating an Array Inside an Object
Copy both the object and the nested array when the original data must remain unchanged.
const course = {
title: "JavaScript",
topics: [
"Variables",
"Arrays"
]
};
const updatedCourse = {
...course,
topics: [
...course.topics,
"Objects"
]
};
console.log(course);
console.log(updatedCourse);
{
title: "JavaScript",
topics: ["Variables", "Arrays"]
}
{
title: "JavaScript",
topics: [
"Variables",
"Arrays",
"Objects"
]
}
Updating an Object Inside an Array
Use map() to replace one record while preserving the other
objects.
const users = [
{
id: 1,
name: "Alice",
active: false
},
{
id: 2,
name: "Bob",
active: false
}
];
const updatedUsers =
users.map(user =>
user.id === 2
? {
...user,
active: true
}
: user
);
console.log(updatedUsers);
[
{
id: 1,
name: "Alice",
active: false
},
{
id: 2,
name: "Bob",
active: true
}
]
Merging Nested Objects
A top-level merge replaces a complete nested object when both sources contain the same property.
const defaults = {
settings: {
theme: "light",
language: "en"
}
};
const preferences = {
settings: {
theme: "dark"
}
};
const merged = {
...defaults,
...preferences
};
console.log(merged);
{
settings: {
theme: "dark"
}
}
The language property disappears because the entire
settings object from preferences replaces the
earlier one.
Manually Merging a Nested Level
const defaults = {
settings: {
theme: "light",
language: "en"
}
};
const preferences = {
settings: {
theme: "dark"
}
};
const merged = {
...defaults,
...preferences,
settings: {
...defaults.settings,
...preferences.settings
}
};
console.log(merged);
{
settings: {
theme: "dark",
language: "en"
}
}
Conditional Object Properties
Spread an object conditionally to include properties only when a condition is true.
const isAdmin = true;
const includeEmail = false;
const user = {
name: "Alice",
...(isAdmin && {
role: "admin"
}),
...(includeEmail && {
email:
"alice@example.com"
})
};
console.log(user);
{
name: "Alice",
role: "admin"
}
Dynamic Property Updates
Computed property names make immutable updates possible when the property key is stored in a variable.
const settings = {
theme: "light",
language: "en"
};
const key = "theme";
const value = "dark";
const updatedSettings = {
...settings,
[key]: value
};
console.log(
updatedSettings
);
{
theme: "dark",
language: "en"
}
Copying Getters and Setters
Spread syntax and Object.assign() read source property values.
They do not preserve complete property descriptors such as getters,
setters, or writable settings.
const source = {
firstName: "Alice",
lastName: "Smith",
get fullName() {
return (
`${this.firstName} ` +
this.lastName
);
}
};
const copy = {
...source
};
console.log(
copy.fullName
);
console.log(
Object.getOwnPropertyDescriptor(
copy,
"fullName"
)
);
Alice Smith
{
value: "Alice Smith",
writable: true,
enumerable: true,
configurable: true
}
Copying Property Descriptors
Use Object.getOwnPropertyDescriptors() together with
Object.defineProperties() when complete descriptors must be
preserved.
const source = {
firstName: "Alice",
lastName: "Smith",
get fullName() {
return (
`${this.firstName} ` +
this.lastName
);
}
};
const copy =
Object.defineProperties(
{},
Object.getOwnPropertyDescriptors(
source
)
);
console.log(
copy.fullName
);
console.log(
typeof Object
.getOwnPropertyDescriptor(
copy,
"fullName"
)
.get
);
Alice Smith
function
Spread and Symbol Properties
Object spread copies own enumerable symbol-keyed properties as well as own enumerable string-keyed properties.
const identifier =
Symbol("id");
const source = {
name: "Alice",
[identifier]: 123
};
const copy = {
...source
};
console.log(
copy[identifier]
);
console.log(
Reflect.ownKeys(copy)
);
123
["name", Symbol(id)]
Non-Enumerable Properties Are Not Copied
const source = {
name: "Alice"
};
Object.defineProperty(
source,
"secret",
{
value: true,
enumerable: false
}
);
const copy = {
...source
};
console.log(copy);
console.log(
copy.secret
);
{
name: "Alice"
}
undefined
Common Copying and Merging Mistakes
- Assuming object spread creates a deep copy.
- Forgetting that later properties overwrite earlier properties.
- Using an existing object as the target of
Object.assign()unintentionally. - Expecting nested objects to merge automatically.
- Updating a nested property without copying its parent objects.
- Assuming spread preserves getters, setters, and property descriptors.
- Assuming non-enumerable properties are included in ordinary copies.
- Using JSON conversion as a universal cloning solution.
Copying and Updating Quick Reference
| Goal | Recommended Pattern |
|---|---|
| Shallow-copy an object | { ...object } |
| Copy with Object.assign() | Object.assign({}, object) |
| Update one property | { ...object, key: value } |
| Update a dynamic property | { ...object, [key]: value } |
| Remove one property | const { key, ...rest } = object |
| Merge objects | { ...first, ...second } |
| Update a nested object | Spread both the outer and nested objects. |
| Add to a nested array | Spread both the outer object and nested array. |
| Preserve property descriptors | Object.defineProperties() with descriptors. |
Use object spread for clear shallow copies and updates. Copy every nested level that must remain independent, keep merge precedence obvious, and use descriptor-based copying only when getters, setters, or property flags must be preserved.
Object Copying and Merging Summary
- Object spread and
Object.assign()create shallow copies. - Later properties overwrite earlier properties with the same key.
Object.assign()mutates its target object.- Nested objects and arrays remain shared in shallow copies.
- Copy every nested level that must become independent.
- Destructuring with rest syntax can omit properties immutably.
- Top-level object merging does not perform a deep merge.
- Computed property names support dynamic updates.
- Ordinary copying does not preserve full property descriptors.
- Object spread copies own enumerable string and symbol properties.
JavaScript Object Destructuring and Iteration
Object destructuring extracts properties into variables using a concise pattern. It supports renamed variables, default values, nested data, rest properties, computed keys, and function parameters.
JavaScript also provides several ways to iterate over objects. Choosing
between Object.keys(), Object.values(),
Object.entries(), and for...in depends on
whether you need keys, values, pairs, or inherited enumerable properties.
Basic Object Destructuring
Property names in the pattern select values from the object and create variables with matching names.
const user = {
name: "Alice",
age: 30,
active: true
};
const {
name,
age,
active
} = user;
console.log(name);
console.log(age);
console.log(active);
Alice
30
true
Destructuring Is Based on Property Names
Unlike array destructuring, object destructuring does not depend on property order.
const product = {
name: "Keyboard",
price: 80,
stock: 12
};
const {
stock,
name
} = product;
console.log(name);
console.log(stock);
Keyboard
12
Renaming Destructured Variables
Use a colon to assign a property value to a variable with a different name.
const user = {
name: "Alice",
age: 30
};
const {
name: userName,
age: userAge
} = user;
console.log(userName);
console.log(userAge);
Alice
30
In { name: userName }, name is the object
property and userName is the new local variable.
Default Values
A default is used when the property value is strictly
undefined.
const user = {
name: "Alice"
};
const {
name,
role = "member",
active = true
} = user;
console.log(name);
console.log(role);
console.log(active);
Alice
member
true
Defaults Do Not Replace null
const settings = {
theme: null,
language: undefined
};
const {
theme = "light",
language = "en"
} = settings;
console.log(theme);
console.log(language);
null
en
When both null and undefined should trigger a
fallback, apply the nullish coalescing operator after destructuring.
const {
theme
} = settings;
const finalTheme =
theme ?? "light";
Renaming and Default Values Together
const user = {
name: "Alice"
};
const {
name: displayName =
"Anonymous",
role: userRole =
"member"
} = user;
console.log(displayName);
console.log(userRole);
Alice
member
Nested Object Destructuring
Nested patterns can extract values from objects inside other objects.
const user = {
name: "Alice",
address: {
city: "London",
country: "UK"
}
};
const {
name,
address: {
city,
country
}
} = user;
console.log(name);
console.log(city);
console.log(country);
Alice
London
UK
The pattern above creates city and country, but
it does not create an address variable.
Safely Destructuring a Missing Nested Object
Provide an empty object as a default when the nested property may be
undefined.
const user = {
name: "Alice"
};
const {
address: {
city = "Unknown"
} = {}
} = user;
console.log(city);
Unknown
The default {} is used only when address is
undefined. If it is explicitly null, nested
destructuring still throws a TypeError.
Destructuring Arrays Inside Objects
const course = {
title: "JavaScript",
topics: [
"Variables",
"Arrays",
"Objects"
]
};
const {
title,
topics: [
firstTopic,
secondTopic
]
} = course;
console.log(title);
console.log(firstTopic);
console.log(secondTopic);
JavaScript
Variables
Arrays
Destructuring Objects Inside Arrays
const users = [
{
id: 1,
name: "Alice"
},
{
id: 2,
name: "Bob"
}
];
const [
{
name: firstName
},
{
name: secondName
}
] = users;
console.log(firstName);
console.log(secondName);
Alice
Bob
Rest Properties
Rest syntax collects the remaining own enumerable properties into a new object.
const user = {
id: 1,
name: "Alice",
age: 30,
active: true
};
const {
id,
...details
} = user;
console.log(id);
console.log(details);
1
{
name: "Alice",
age: 30,
active: true
}
Excluding Several Properties
const user = {
id: 1,
name: "Alice",
email:
"alice@example.com",
password: "secret",
token: "abc123"
};
const {
password,
token,
...publicUser
} = user;
console.log(publicUser);
{
id: 1,
name: "Alice",
email: "alice@example.com"
}
Nested objects and arrays remain shared references, just as they do with ordinary object spread.
Computed Property Destructuring
A property key stored in a variable can be used inside a destructuring pattern.
const settings = {
theme: "dark",
language: "en"
};
const key = "theme";
const {
[key]: selectedValue
} = settings;
console.log(
selectedValue
);
dark
Destructuring an Existing Variable
Wrap an assignment pattern in parentheses when assigning to variables that already exist.
let name;
let age;
const user = {
name: "Alice",
age: 30
};
({
name,
age
} = user);
console.log(name);
console.log(age);
Alice
30
Without parentheses, JavaScript may interpret the opening brace as a code block instead of a destructuring assignment.
Destructuring Function Parameters
Functions can destructure an object directly in the parameter list.
function displayUser({
name,
age,
active
}) {
console.log(
`${name}, ${age}, ${active}`
);
}
displayUser({
name: "Alice",
age: 30,
active: true
});
Alice, 30, true
Parameter Defaults
function createButton({
text = "Submit",
type = "button",
disabled = false
} = {}) {
return {
text,
type,
disabled
};
}
console.log(
createButton({
text: "Save"
})
);
console.log(
createButton()
);
{
text: "Save",
type: "button",
disabled: false
}
{
text: "Submit",
type: "button",
disabled: false
}
The outer default allows the function to be called without an argument.
Without it, destructuring undefined would throw a
TypeError.
Renaming Destructured Parameters
function printProduct({
name: productName,
price: productPrice
}) {
console.log(
`${productName}: $${productPrice}`
);
}
printProduct({
name: "Keyboard",
price: 80
});
Keyboard: $80
Nested Parameter Destructuring
function printLocation({
name,
address: {
city = "Unknown",
country = "Unknown"
} = {}
}) {
console.log(
`${name}: ${city}, ${country}`
);
}
printLocation({
name: "Alice",
address: {
city: "London",
country: "UK"
}
});
Alice: London, UK
Returning Multiple Named Values
Functions can return an object so callers can extract only the properties they need.
function calculate(
first,
second
) {
return {
sum: first + second,
difference:
first - second,
product:
first * second
};
}
const {
sum,
product
} = calculate(10, 4);
console.log(sum);
console.log(product);
14
40
Iterating with Object.keys()
Use Object.keys() when you primarily need the property names.
const user = {
name: "Alice",
age: 30,
active: true
};
for (
const key of
Object.keys(user)
) {
console.log(
`${key}: ${user[key]}`
);
}
name: Alice
age: 30
active: true
Iterating with Object.values()
Use Object.values() when property names are irrelevant.
const scores = {
testOne: 82,
testTwo: 91,
testThree: 87
};
let total = 0;
for (
const score of
Object.values(scores)
) {
total += score;
}
console.log(total);
260
Iterating with Object.entries()
Use entries when both the key and value are needed.
const settings = {
theme: "dark",
language: "en",
notifications: true
};
for (
const [key, value] of
Object.entries(settings)
) {
console.log(
key,
value
);
}
theme dark
language en
notifications true
The for...in Loop
A for...in loop iterates over enumerable string-keyed
properties, including inherited ones.
const user = {
name: "Alice",
age: 30
};
for (const key in user) {
console.log(
key,
user[key]
);
}
name Alice
age 30
Inherited Properties with for...in
const baseUser = {
role: "member"
};
const user =
Object.create(baseUser);
user.name = "Alice";
user.age = 30;
for (const key in user) {
console.log(
key,
user[key]
);
}
name Alice
age 30
role member
Safe for...in Iteration
Filter with Object.hasOwn() when only direct properties should
be processed.
const baseUser = {
role: "member"
};
const user =
Object.create(baseUser);
user.name = "Alice";
user.age = 30;
for (const key in user) {
if (
Object.hasOwn(
user,
key
)
) {
console.log(
key,
user[key]
);
}
}
name Alice
age 30
For ordinary application objects, Object.entries() with
for...of is often clearer because it automatically limits
iteration to own enumerable string-keyed properties.
Object Iteration Comparison
| Technique | Produces | Inherited Properties? | Symbol Properties? |
|---|---|---|---|
Object.keys() |
Array of keys | No | No |
Object.values() |
Array of values | No | No |
Object.entries() |
Array of key-value pairs | No | No |
for...in |
Property keys | Yes | No |
Reflect.ownKeys() |
All own keys | No | Yes |
Transforming an Object with Entries
Convert an object to entries, use array methods, and reconstruct the result.
const prices = {
laptop: 1200,
keyboard: 80,
mouse: 40
};
const increasedPrices =
Object.fromEntries(
Object.entries(prices)
.map(
([key, value]) => [
key,
value * 1.1
]
)
);
console.log(
increasedPrices
);
{
laptop: 1320,
keyboard: 88,
mouse: 44
}
Filtering an Object by Value
const features = {
search: true,
export: false,
analytics: true,
comments: false
};
const enabledFeatures =
Object.fromEntries(
Object.entries(features)
.filter(
([, enabled]) =>
enabled
)
);
console.log(
enabledFeatures
);
{
search: true,
analytics: true
}
Sorting Object Entries
Although objects are not primarily sorted collections, entries can be sorted before processing or reconstruction.
const scores = {
Alice: 82,
Bob: 95,
Maya: 88
};
const sortedEntries =
Object.entries(scores)
.toSorted(
(
[, firstScore],
[, secondScore]
) =>
secondScore -
firstScore
);
console.log(
sortedEntries
);
[
["Bob", 95],
["Maya", 88],
["Alice", 82]
]
Creating a Lookup Object with reduce()
Convert an array of records into an object indexed by a unique property.
const users = [
{
id: 1,
name: "Alice"
},
{
id: 2,
name: "Bob"
}
];
const usersById =
users.reduce(
(result, user) => {
result[user.id] =
user;
return result;
},
{}
);
console.log(
usersById[2]
);
{
id: 2,
name: "Bob"
}
A Map may be more appropriate when keys are not strings,
insertion order is important, or the collection changes frequently.
Grouping Records into an Object
const products = [
{
name: "Laptop",
category: "Tech"
},
{
name: "Mouse",
category: "Tech"
},
{
name: "Chair",
category: "Furniture"
}
];
const grouped =
products.reduce(
(result, product) => {
const {
category
} = product;
result[category] ??= [];
result[category].push(
product
);
return result;
},
{}
);
console.log(grouped);
{
Tech: [
{
name: "Laptop",
category: "Tech"
},
{
name: "Mouse",
category: "Tech"
}
],
Furniture: [
{
name: "Chair",
category: "Furniture"
}
]
}
Counting Object Values
const tasks = {
first: "complete",
second: "pending",
third: "complete",
fourth: "failed"
};
const counts =
Object.values(tasks)
.reduce(
(result, status) => {
result[status] =
(
result[status] ??
0
) + 1;
return result;
},
{}
);
console.log(counts);
{
complete: 2,
pending: 1,
failed: 1
}
Destructuring and Iteration Mistakes
- Confusing property aliases with object key-value syntax.
- Expecting destructuring defaults to replace
null. - Destructuring a missing nested object without a safe default.
- Assuming nested destructuring creates the parent variable.
- Forgetting parentheses when assigning to existing variables.
- Calling a destructured parameter function without a safe outer default.
- Using
for...inwithout considering inherited properties. - Expecting standard key, value, and entry methods to include symbols.
- Creating overly complex destructuring patterns that reduce readability.
Destructuring Quick Reference
| Goal | Pattern |
|---|---|
| Extract a property | const { name } = object |
| Rename a variable | const { name: userName } = object |
| Provide a default | const { role = "member" } = object |
| Rename with a default | const { role: userRole = "member" } = object |
| Extract a nested property | const { address: { city } } = object |
| Safely destructure a missing nested object | const { address: { city } = {} } = object |
| Collect remaining properties | const { id, ...rest } = object |
| Extract a dynamic key | const { [key]: value } = object |
| Destructure a function parameter | function run({ name } = {}) {} |
| Assign to existing variables | ({ name } = object) |
Use destructuring when it makes required properties clear, but avoid
deeply nested patterns that are difficult to read. Prefer
Object.entries() for straightforward own-property iteration,
and use Object.hasOwn() when a
for...in loop must exclude inherited properties.
Object Destructuring and Iteration Summary
- Object destructuring extracts properties by name.
- Properties can be renamed and assigned defaults.
- Defaults apply only when a value is
undefined. - Nested patterns extract values from nested objects and arrays.
- Rest syntax collects the remaining own enumerable properties.
- Function parameters can destructure configuration objects directly.
Object.keys(),values(), andentries()support array-style processing.for...inalso visits inherited enumerable properties.- Entry pipelines make it possible to filter and transform objects.
- Objects can be indexed, grouped, and summarized from array data.
JavaScript Object Descriptors, Immutability and Cloning
Use property descriptors to control object properties,
Object.freeze() to prevent direct top-level changes, and
structuredClone() to create deep copies of supported data.
| API | Purpose | Important limitation |
|---|---|---|
Object.defineProperty() |
Defines a property with explicit descriptor settings. | Omitted descriptor flags default to false. |
Object.getOwnPropertyDescriptor() |
Reads a property's descriptor. | Only checks an own property. |
Object.freeze() |
Prevents adding, deleting, or changing top-level properties. | Nested objects are not frozen automatically. |
structuredClone() |
Deep-clones many built-in and nested data types. | Functions and some platform objects cannot be cloned. |
const settings = {};
Object.defineProperty(settings, "version", {
value: 1,
writable: false,
enumerable: true
});
const frozen = Object.freeze({ theme: "dark" });
const original = { user: { name: "Ava" } };
const copy = structuredClone(original);
structuredClone() only when the contained values are
supported and a true deep copy is required.
JavaScript Functions
JavaScript functions are reusable blocks of code designed to perform a specific task. They help organize programs, reduce duplicated code, separate responsibilities, and make complex applications easier to test and maintain.
Functions can receive input through parameters, process that input, and return a result. JavaScript treats functions as values, which means they can be stored in variables, passed to other functions, returned from functions, and stored inside objects or arrays.
Why Use Functions?
| Benefit | Description |
|---|---|
| Reusability | Write logic once and execute it many times. |
| Organization | Divide large programs into smaller named tasks. |
| Maintainability | Update shared behavior in one location. |
| Readability | Use descriptive names to explain what code does. |
| Testing | Test isolated pieces of application logic. |
| Abstraction | Hide implementation details behind a clear interface. |
Function Declaration
A function declaration uses the function keyword followed by a
function name, parentheses, and a function body.
function greet() {
console.log("Hello!");
}
greet();
Hello!
Function Declaration Structure
| Part | Example | Purpose |
|---|---|---|
| Keyword | function |
Begins a traditional function declaration. |
| Name | greet |
Identifies the function. |
| Parameters | () |
Defines the function's input variables. |
| Body | { ... } |
Contains statements executed when the function is called. |
| Call | greet() |
Executes the function. |
Defining vs Calling a Function
Defining a function creates it. The code inside the function body does not execute until the function is called.
function showMessage() {
console.log(
"Function executed"
);
}
console.log(
"Before the call"
);
showMessage();
console.log(
"After the call"
);
Before the call
Function executed
After the call
Calling a Function Multiple Times
After a function has been defined, it can be called whenever its behavior is required.
function showWelcome() {
console.log(
"Welcome to CheatSheetSilo"
);
}
showWelcome();
showWelcome();
showWelcome();
Welcome to CheatSheetSilo
Welcome to CheatSheetSilo
Welcome to CheatSheetSilo
Function Names
Function names follow normal JavaScript identifier rules. Descriptive verb-based names usually communicate the function's purpose clearly.
| Name | Quality | Reason |
|---|---|---|
calculateTotal |
Good | Clearly describes an action and result. |
getUserById |
Good | Explains what is returned and how it is selected. |
isValidEmail |
Good | Suggests a Boolean result. |
handleSubmit |
Good | Communicates event-handling responsibility. |
doStuff |
Poor | Does not explain the function's purpose. |
function1 |
Poor | Provides no useful meaning. |
Functions usually use camel case and begin with a verb, such as
createUser, formatPrice,
validateForm, or sendRequest.
Function Hoisting
Function declarations are hoisted, allowing them to be called before their declaration appears in the source code.
greet();
function greet() {
console.log(
"Hello from a declaration"
);
}
Hello from a declaration
Although declarations can be called before they appear, defining a function before its first use often makes the execution flow easier to follow.
Function Parameters
Parameters are local variables listed in the function definition. They describe the input values the function expects.
function greet(name) {
console.log(
`Hello, ${name}!`
);
}
greet("Alice");
greet("Bob");
Hello, Alice!
Hello, Bob!
Parameters vs Arguments
| Term | Location | Example |
|---|---|---|
| Parameter | Function definition | name in function greet(name) |
| Argument | Function call | "Alice" in greet("Alice") |
Multiple Parameters
Separate multiple parameters with commas. Arguments are assigned according to their position.
function introduce(
name,
role,
experience
) {
console.log(
`${name} is a ${role} ` +
`with ${experience} years ` +
"of experience."
);
}
introduce(
"Alice",
"developer",
5
);
Alice is a developer with 5 years of experience.
Argument Order Matters
function subtract(
first,
second
) {
return first - second;
}
console.log(
subtract(10, 4)
);
console.log(
subtract(4, 10)
);
6
-6
Missing Arguments
When an argument is omitted, its corresponding parameter receives
undefined.
function showUser(
name,
role
) {
console.log(name);
console.log(role);
}
showUser("Alice");
Alice
undefined
Extra Arguments
JavaScript allows more arguments than declared parameters. Unused extra
arguments are normally ignored unless accessed through rest parameters or
the traditional arguments object.
function greet(name) {
console.log(
`Hello, ${name}!`
);
}
greet(
"Alice",
30,
true
);
Hello, Alice!
The return Statement
The return statement ends the current function call and sends
a value back to the code that called it.
function add(
first,
second
) {
return first + second;
}
const result =
add(10, 5);
console.log(result);
15
Returned Values Can Be Reused
function calculatePrice(
price,
quantity
) {
return price * quantity;
}
const subtotal =
calculatePrice(
25,
4
);
const totalWithTax =
subtotal * 1.2;
console.log(subtotal);
console.log(
totalWithTax
);
100
120
return Stops Function Execution
Statements after an executed return are not reached.
function getAccessMessage(
isLoggedIn
) {
if (!isLoggedIn) {
return "Please log in.";
}
return "Welcome back.";
}
console.log(
getAccessMessage(false)
);
console.log(
getAccessMessage(true)
);
Please log in.
Welcome back.
Functions Without an Explicit Return
A function that reaches the end without executing a
return statement returns undefined.
function logMessage() {
console.log(
"Message logged"
);
}
const result =
logMessage();
console.log(result);
Message logged
undefined
Returning Multiple Values
A function returns one value, but that value can be an object or array containing several related results.
function calculate(
first,
second
) {
return {
sum: first + second,
difference:
first - second,
product:
first * second
};
}
const result =
calculate(10, 4);
console.log(result);
{
sum: 14,
difference: 6,
product: 40
}
Function Expression
A function expression creates a function as part of an expression and commonly assigns it to a variable.
const greet =
function () {
console.log(
"Hello from an expression"
);
};
greet();
Hello from an expression
Function Expressions Are Values
The function object is assigned to the variable. The variable can then be used to call or pass the function.
const multiply =
function (
first,
second
) {
return first * second;
};
console.log(
multiply(6, 7)
);
42
Function Expression Hoisting
A function expression assigned to const or let
cannot be called before the variable initialization.
greet();
const greet =
function () {
console.log("Hello");
};
Calling greet() before the variable is initialized throws a
ReferenceError.
Named Function Expression
A function expression may include its own internal name. This name is particularly useful for stack traces and recursion.
const calculateFactorial =
function factorial(
number
) {
if (number <= 1) {
return 1;
}
return (
number *
factorial(
number - 1
)
);
};
console.log(
calculateFactorial(5)
);
120
Internal Function Name Scope
The internal name of a named function expression is normally available only inside that function.
const run =
function internalName() {
console.log(
typeof internalName
);
};
run();
console.log(
typeof internalName
);
function
undefined
Declaration vs Expression
| Feature | Function Declaration | Function Expression |
|---|---|---|
| Basic syntax | function greet() {} |
const greet = function () {}; |
| Callable before definition | Yes | No with const or let |
| Can be anonymous | No | Yes |
| Stored as a value | Functions are values | Explicitly assigned as a value |
| Useful for callbacks | Yes | Very common |
| Conditional creation | Avoid relying on block behavior | Suitable as part of an expression |
Anonymous Functions
An anonymous function has no explicit name after the
function keyword. Anonymous functions are commonly used as
callbacks and assigned values.
const numbers = [
1,
2,
3
];
const doubled =
numbers.map(
function (number) {
return number * 2;
}
);
console.log(doubled);
[2, 4, 6]
Named functions can produce clearer stack traces. Use descriptive names for complex callbacks or functions reused in several locations.
Arrow Functions
Arrow functions provide a shorter syntax for function expressions. They are especially common for callbacks and concise transformation logic.
const greet = () => {
console.log(
"Hello from an arrow function"
);
};
greet();
Hello from an arrow function
Arrow Function with One Parameter
Parentheses around one simple parameter are optional.
const square =
number => {
return number * number;
};
console.log(
square(5)
);
25
Arrow Function with Multiple Parameters
Parentheses are required for zero parameters, multiple parameters, default parameters, rest parameters, or destructured parameters.
const add =
(first, second) => {
return first + second;
};
console.log(
add(10, 5)
);
15
Implicit Return
When an arrow function contains one expression without braces, the expression's value is returned automatically.
const multiply =
(first, second) =>
first * second;
console.log(
multiply(6, 7)
);
42
Explicit vs Implicit Return
| Style | Syntax | Return Behavior |
|---|---|---|
| Block body | value => { return value * 2; } |
Requires an explicit return. |
| Expression body | value => value * 2 |
Returns the expression automatically. |
Returning an Object Literal
Wrap an object literal in parentheses when returning it implicitly. Otherwise, braces are interpreted as the function body.
const createUser =
(name, age) => ({
name,
age,
active: true
});
console.log(
createUser(
"Alice",
30
)
);
{
name: "Alice",
age: 30,
active: true
}
const createUser =
name => {
name
};
This does not return an object. The braces define a function body, and no
return statement is present.
Arrow Functions as Array Callbacks
const prices = [
10,
20,
30
];
const pricesWithTax =
prices.map(
price =>
price * 1.2
);
console.log(
pricesWithTax
);
[12, 24, 36]
Arrow Functions Are Not Always Interchangeable
Arrow functions differ from traditional functions in several important ways.
| Feature | Traditional Function | Arrow Function |
|---|---|---|
Own this |
Depends on how the function is called. | No; inherits lexical this. |
Own arguments |
Yes for non-arrow functions. | No |
Usable with new |
Yes when constructable. | No |
prototype property for construction |
Available on constructable functions. | Not available. |
| Generator syntax | Supported with function*. |
Not supported. |
| Concise expression return | No special implicit syntax. | Yes |
const user = {
name: "Alice",
greet: () => {
return `Hello ${this.name}`;
}
};
The arrow function does not receive user as its
this value. Use method shorthand when the method needs
dynamic object context.
Function Syntax Comparison
| Type | Example | Common Use |
|---|---|---|
| Function declaration | function add(a, b) { return a + b; } |
Named reusable application logic. |
| Anonymous expression | const add = function (a, b) { return a + b; }; |
Functions stored as values or callbacks. |
| Named expression | const add = function calculate(a, b) { ... }; |
Recursion and clearer debugging. |
| Arrow function | const add = (a, b) => a + b; |
Concise callbacks and lexical this. |
| Object method | const object = { run() {} }; |
Behavior associated with an object. |
Use declarations for clear named reusable operations, arrow functions for
concise callbacks, and method shorthand for object behavior that relies
on this. Choose the style that communicates the function's
role most clearly.
Functions Block 1A Summary
- Functions package reusable behavior into callable values.
- Function declarations use the
functionkeyword and are hoisted. - Parameters define expected input, while arguments provide actual values.
- The
returnstatement sends a result back and ends execution. - A function without an executed return statement returns
undefined. - Function expressions store functions as values.
- Named function expressions improve recursion and debugging.
- Arrow functions provide concise expression syntax.
- Implicit returns work only with arrow expression bodies.
- Object literals need parentheses when returned implicitly.
- Arrow functions do not have their own
thisorarguments. - Different function styles are suited to different responsibilities.
JavaScript Default Parameters, Rest Parameters and Spread Arguments
JavaScript functions support flexible parameter syntax for optional values, variable numbers of arguments, and array-based function calls. Default parameters provide fallback values, rest parameters collect arguments into an array, and spread syntax expands iterable values into individual arguments.
These features make function interfaces easier to understand and reduce the need for manual argument handling.
Default Parameters
A default parameter provides a fallback value when the corresponding
argument is omitted or explicitly set to undefined.
function greet(
name = "Guest"
) {
return `Hello, ${name}!`;
}
console.log(
greet("Alice")
);
console.log(
greet()
);
Hello, Alice!
Hello, Guest!
When Is a Default Used?
| Argument | Default Used? | Parameter Value |
|---|---|---|
| Argument omitted | Yes | Default value |
undefined |
Yes | Default value |
null |
No | null |
false |
No | false |
0 |
No | 0 |
| Empty string | No | "" |
function showValue(
value = "default"
) {
console.log(value);
}
showValue();
showValue(undefined);
showValue(null);
showValue(false);
showValue(0);
showValue("");
default
default
null
false
0
A default parameter activates only for a missing argument or
undefined. Use the nullish coalescing operator
?? when both null and
undefined should trigger a fallback.
Multiple Default Parameters
function createUser(
name = "Anonymous",
role = "member",
active = true
) {
return {
name,
role,
active
};
}
console.log(
createUser()
);
console.log(
createUser(
"Alice",
"admin",
false
)
);
{
name: "Anonymous",
role: "member",
active: true
}
{
name: "Alice",
role: "admin",
active: false
}
Skipping an Earlier Optional Argument
Pass undefined to activate an earlier default while supplying
a later positional argument.
function createMessage(
text = "Hello",
type = "info"
) {
return `[${type}] ${text}`;
}
console.log(
createMessage(
undefined,
"warning"
)
);
[warning] Hello
When a function has several optional settings, an object parameter is
often clearer than passing undefined through multiple
positional arguments.
Default Parameters Can Use Expressions
A default value is evaluated when the function is called and the
corresponding argument is missing or undefined.
function createRecord(
createdAt = new Date()
) {
return {
createdAt
};
}
const record =
createRecord();
console.log(
record.createdAt
instanceof Date
);
true
Calling a Function in a Default Parameter
function createIdentifier() {
return Math.floor(
Math.random() * 1000
);
}
function createUser(
name,
id = createIdentifier()
) {
return {
id,
name
};
}
console.log(
createUser("Alice")
);
createIdentifier() runs only when the id
argument is omitted or explicitly set to undefined.
Using Earlier Parameters in Later Defaults
A later default parameter can refer to a parameter declared before it.
function calculateTotal(
price,
quantity = 1,
tax = price *
quantity *
0.2
) {
return (
price *
quantity +
tax
);
}
console.log(
calculateTotal(
100,
2
)
);
240
A default parameter cannot safely use a later parameter before that later parameter has been initialized.
// Avoid
function example(
first = second,
second = 10
) {
return first + second;
}
Required Parameters with a Helper
JavaScript has no dedicated required-parameter syntax, but a default expression can throw an error when an argument is missing.
function required(
parameterName
) {
throw new TypeError(
`${parameterName} is required.`
);
}
function createProduct(
name = required("name"),
price = required("price")
) {
return {
name,
price
};
}
console.log(
createProduct(
"Keyboard",
80
)
);
{
name: "Keyboard",
price: 80
}
Rest Parameters
A rest parameter collects all remaining arguments into a real array. It uses three dots before the parameter name.
function showValues(
...values
) {
console.log(values);
}
showValues(
10,
20,
30,
40
);
[10, 20, 30, 40]
Rest Parameters Are Real Arrays
Rest parameters support array methods such as map(),
filter(), and reduce() directly.
function sum(
...numbers
) {
return numbers.reduce(
(total, number) =>
total + number,
0
);
}
console.log(
sum(5, 10, 15)
);
console.log(
sum(1, 2, 3, 4, 5)
);
30
15
Named Parameters Before a Rest Parameter
Regular parameters may appear before the rest parameter. The rest parameter collects only the arguments that remain.
function createMessage(
sender,
...recipients
) {
return {
sender,
recipients
};
}
console.log(
createMessage(
"Alice",
"Bob",
"Maya",
"David"
)
);
{
sender: "Alice",
recipients: [
"Bob",
"Maya",
"David"
]
}
The Rest Parameter Must Be Last
// SyntaxError
function invalid(
...values,
finalValue
) {
return values;
}
A function can have only one rest parameter, and it must be the final parameter.
Rest Parameters with Arrow Functions
const multiplyAll =
(...numbers) =>
numbers.reduce(
(product, number) =>
product * number,
1
);
console.log(
multiplyAll(
2,
3,
4
)
);
24
Filtering Rest Arguments
function average(
...values
) {
const numbers =
values.filter(
value =>
typeof value ===
"number" &&
Number.isFinite(value)
);
if (
numbers.length === 0
) {
return 0;
}
const total =
numbers.reduce(
(sum, number) =>
sum + number,
0
);
return (
total /
numbers.length
);
}
console.log(
average(
10,
"20",
30,
NaN,
50
)
);
30
Rest Syntax in Parameters
In a function definition, rest syntax collects separate arguments into one array.
function collect(
...items
) {
return items;
}
const result =
collect(
"HTML",
"CSS",
"JavaScript"
);
console.log(result);
["HTML", "CSS", "JavaScript"]
Spread Syntax in Function Calls
In a function call, spread syntax expands an iterable into separate arguments.
function add(
first,
second,
third
) {
return (
first +
second +
third
);
}
const numbers = [
10,
20,
30
];
console.log(
add(...numbers)
);
60
Rest vs Spread Syntax
| Syntax | Location | Behavior |
|---|---|---|
...values |
Function parameter list | Collects remaining arguments into an array. |
...values |
Function call | Expands an iterable into separate arguments. |
...values |
Array literal | Expands iterable elements into a new array. |
...object |
Object literal | Copies own enumerable properties into a new object. |
Combining Regular and Spread Arguments
function createRange(
start,
middle,
end
) {
return [
start,
middle,
end
];
}
const middleValues = [
5
];
console.log(
createRange(
1,
...middleValues,
10
)
);
[1, 5, 10]
Math Methods with Spread Syntax
Spread syntax is commonly used to pass array values to methods expecting separate numeric arguments.
const scores = [
82,
95,
71,
88
];
const lowest =
Math.min(...scores);
const highest =
Math.max(...scores);
console.log(lowest);
console.log(highest);
71
95
Spreading an extremely large array into a function call may exceed the
JavaScript engine's argument limit. Use a loop or
reduce() for very large collections.
Forwarding Arguments
Rest and spread syntax work together when one function forwards all its received arguments to another function.
function calculateTotal(
price,
quantity,
taxRate
) {
const subtotal =
price * quantity;
return (
subtotal +
subtotal * taxRate
);
}
function logTotal(
...argumentsToForward
) {
const total =
calculateTotal(
...argumentsToForward
);
console.log(total);
return total;
}
logTotal(
100,
2,
0.2
);
240
Combining Several Arrays as Arguments
function listSkills(
...skills
) {
return skills.join(", ");
}
const frontend = [
"HTML",
"CSS"
];
const programming = [
"JavaScript",
"Python"
];
console.log(
listSkills(
...frontend,
...programming,
"SQL"
)
);
HTML, CSS, JavaScript, Python, SQL
Spreading Strings into Arguments
Strings are iterable, so spread syntax expands them into individual characters or Unicode code points as function arguments.
function joinCharacters(
...characters
) {
return characters.join("-");
}
console.log(
joinCharacters(
..."Java"
)
);
J-a-v-a
Default and Rest Parameters Together
function formatValues(
separator = ", ",
...values
) {
return values.join(
separator
);
}
console.log(
formatValues(
" | ",
"JavaScript",
"Python",
"SQL"
)
);
JavaScript | Python | SQL
The Function length Property
A function's length property reports the number of parameters
before the first parameter with a default value. Rest parameters are not
counted.
function first(
a,
b,
c
) {}
function second(
a,
b = 10,
c
) {}
function third(
a,
...values
) {}
console.log(
first.length
);
console.log(
second.length
);
console.log(
third.length
);
3
1
1
JavaScript does not enforce a function's declared parameter count.
Callers may provide fewer or more arguments than the function's
length value.
Parameter Feature Comparison
| Feature | Syntax | Purpose |
|---|---|---|
| Default parameter | value = fallback |
Provide a fallback for missing or undefined arguments. |
| Rest parameter | ...values |
Collect remaining arguments into an array. |
| Spread argument | run(...values) |
Expand an iterable into separate arguments. |
| Required helper | value = required("value") |
Throw when a required argument is omitted. |
| Dependent default | total = price * quantity |
Calculate a default from earlier parameters. |
Common Default, Rest and Spread Mistakes
- Expecting a default parameter to replace
null. - Placing a rest parameter before another parameter.
- Declaring more than one rest parameter.
- Confusing rest collection with spread expansion.
- Forgetting that spread requires an iterable in function calls.
- Spreading extremely large arrays into function calls.
- Using many optional positional parameters when an options object would be clearer.
- Using a later parameter inside an earlier default expression.
- Assuming a function's
lengthproperty enforces argument count.
Use default parameters for simple optional values, rest parameters when a function accepts a variable number of related arguments, and spread syntax when existing iterable values must be passed as individual arguments. Prefer an options object when several independent settings are optional.
Default, Rest and Spread Summary
- Default parameters activate for omitted arguments and
undefined. - Values such as
null,false,0, and empty strings do not activate defaults. - Default expressions are evaluated when the function is called.
- Later default parameters may use earlier parameter values.
- A required-parameter helper can throw when an argument is missing.
- Rest parameters collect remaining arguments into a real array.
- A rest parameter must be the final parameter.
- Spread syntax expands iterable values into separate function arguments.
- Rest and spread syntax can forward arguments between functions.
- Spread syntax is useful with methods such as
Math.min()andMath.max(). - Options objects are often clearer than many optional positional arguments.
JavaScript Callbacks, Closures and Functional Patterns
Because JavaScript functions are values, they can be passed to other functions, returned as results, stored in objects, and executed later. This behavior makes callbacks, higher-order functions, and closures possible.
These patterns are widely used in array processing, event handling, timers, reusable utilities, configuration, asynchronous code, and modern application architecture.
Functions Are First-Class Values
JavaScript treats functions like other values. A function can be assigned to a variable, passed as an argument, or returned from another function.
function greet(name) {
return `Hello, ${name}!`;
}
const sayHello = greet;
console.log(
sayHello("Alice")
);
console.log(
sayHello === greet
);
Hello, Alice!
true
Writing greet refers to the function itself. Writing
greet() immediately calls it and produces its return value.
Callback Functions
A callback is a function passed to another function so it can be executed at an appropriate time.
function processUser(
name,
callback
) {
const normalizedName =
name.trim();
return callback(
normalizedName
);
}
function createGreeting(name) {
return `Hello, ${name}!`;
}
console.log(
processUser(
" Alice ",
createGreeting
)
);
Hello, Alice!
Passing a Function Correctly
// Passes the function
processUser(
"Alice",
createGreeting
);
// Passes the returned string
processUser(
"Alice",
createGreeting("Alice")
);
The second example executes createGreeting() immediately and
passes its returned string instead of passing the function.
Inline Callback Functions
A callback can be declared directly inside the function call.
function transform(
value,
callback
) {
return callback(value);
}
const result =
transform(
10,
number =>
number * 2
);
console.log(result);
20
Named vs Inline Callbacks
| Callback Style | Best Used For |
|---|---|
| Inline arrow function | Short logic used in one location. |
| Named function | Reusable, complex, or independently testable logic. |
| Named function expression | Callbacks requiring recursion or clearer stack traces. |
| Anonymous traditional function | Callbacks requiring a dynamic this value. |
Callbacks with Array Methods
Many array methods receive callback functions that determine how each element should be processed.
const numbers = [
1,
2,
3,
4,
5
];
const doubled =
numbers.map(
number =>
number * 2
);
const evenNumbers =
numbers.filter(
number =>
number % 2 === 0
);
console.log(doubled);
console.log(evenNumbers);
[2, 4, 6, 8, 10]
[2, 4]
Callbacks with Timers
Timer functions accept callbacks that are executed after a delay or at repeated intervals.
console.log("Start");
setTimeout(
() => {
console.log(
"Timer completed"
);
},
1000
);
console.log("End");
Start
End
Timer completed
The delay specifies the minimum time before the callback becomes eligible to run. Other JavaScript work may delay its actual execution.
Higher-Order Functions
A higher-order function receives one or more functions, returns a function, or does both.
function calculate(
first,
second,
operation
) {
return operation(
first,
second
);
}
function add(
first,
second
) {
return first + second;
}
function multiply(
first,
second
) {
return first * second;
}
console.log(
calculate(10, 5, add)
);
console.log(
calculate(
10,
5,
multiply
)
);
15
50
Returning a Function
A function can create and return another function with customized behavior.
function createMultiplier(
multiplier
) {
return function (
number
) {
return (
number * multiplier
);
};
}
const double =
createMultiplier(2);
const triple =
createMultiplier(3);
console.log(
double(5)
);
console.log(
triple(5)
);
10
15
Closures
A closure is created when a function remembers variables from the lexical scope where it was defined, even after that outer function has completed.
function createGreeting(
greeting
) {
return function (name) {
return (
`${greeting}, ${name}!`
);
};
}
const sayHello =
createGreeting("Hello");
const sayWelcome =
createGreeting("Welcome");
console.log(
sayHello("Alice")
);
console.log(
sayWelcome("Bob")
);
Hello, Alice!
Welcome, Bob!
Closure Counter
Closures can preserve private state between function calls.
function createCounter() {
let count = 0;
return function () {
count += 1;
return count;
};
}
const counter =
createCounter();
console.log(counter());
console.log(counter());
console.log(counter());
1
2
3
Independent Closure State
Every call to the outer function creates a separate lexical environment.
function createCounter() {
let count = 0;
return () => {
count += 1;
return count;
};
}
const firstCounter =
createCounter();
const secondCounter =
createCounter();
console.log(
firstCounter()
);
console.log(
firstCounter()
);
console.log(
secondCounter()
);
1
2
1
Closure with Several Methods
function createAccount(
initialBalance = 0
) {
let balance =
initialBalance;
return {
deposit(amount) {
if (
amount <= 0
) {
return balance;
}
balance += amount;
return balance;
},
withdraw(amount) {
if (
amount <= 0 ||
amount > balance
) {
return false;
}
balance -= amount;
return true;
},
getBalance() {
return balance;
}
};
}
const account =
createAccount(100);
account.deposit(50);
account.withdraw(30);
console.log(
account.getBalance()
);
120
The balance variable cannot be accessed directly from
outside createAccount(). It is available only through the
returned methods.
Closures Inside Loops
Variables declared with let create a new binding for each loop
iteration.
const functions = [];
for (
let index = 0;
index < 3;
index++
) {
functions.push(
() => index
);
}
console.log(
functions[0]()
);
console.log(
functions[1]()
);
console.log(
functions[2]()
);
0
1
2
A loop using var creates one shared function-scoped binding,
so delayed callbacks may all observe the final value. Prefer
let for loop counters.
Closure Memory Considerations
Values referenced by a closure may remain in memory for as long as the closure itself remains reachable.
Long-lived callbacks, event listeners, and timers should not capture large objects unless they are genuinely needed. Remove listeners and clear timers when their work is complete.
Pure Functions
A pure function returns the same result for the same inputs and does not produce observable side effects.
function calculateTax(
price,
taxRate
) {
return (
price * taxRate
);
}
console.log(
calculateTax(
100,
0.2
)
);
console.log(
calculateTax(
100,
0.2
)
);
20
20
Pure Function Characteristics
| Characteristic | Description |
|---|---|
| Deterministic | The same inputs produce the same result. |
| No argument mutation | Input objects and arrays remain unchanged. |
| No external mutation | The function does not modify external variables or application state. |
| No hidden dependencies | The result depends primarily on explicit parameters. |
| Easy to test | Inputs and outputs can be tested directly. |
Avoiding Argument Mutation
function applyDiscount(
product,
percentage
) {
return {
...product,
price:
product.price *
(1 - percentage)
};
}
const original = {
name: "Keyboard",
price: 100
};
const discounted =
applyDiscount(
original,
0.2
);
console.log(original);
console.log(discounted);
{
name: "Keyboard",
price: 100
}
{
name: "Keyboard",
price: 80
}
Side Effects
A side effect is an observable change outside a function's returned value. Side effects are necessary in real applications, but should be intentional and controlled.
| Common Side Effect | Example |
|---|---|
| Changing external state | Updating a variable declared outside the function. |
| Mutating an argument | Changing a passed object or array. |
| DOM manipulation | Changing text or elements on a webpage. |
| Network communication | Sending or receiving data through an API. |
| Storage operations | Writing to local storage or a database. |
| Logging | Writing output with console.log(). |
| Timers | Scheduling work with setTimeout(). |
Function with an External Side Effect
let total = 0;
function addToTotal(
amount
) {
total += amount;
return total;
}
console.log(
addToTotal(10)
);
console.log(
addToTotal(10)
);
10
20
Separating Calculation from Side Effects
Keep calculations pure where practical, then perform external effects in a clearly identified location.
function calculateTotal(
items
) {
return items.reduce(
(total, item) =>
total +
item.price *
item.quantity,
0
);
}
function displayTotal(
total
) {
console.log(
`Total: $${total}`
);
}
const items = [
{
price: 20,
quantity: 2
},
{
price: 10,
quantity: 3
}
];
const total =
calculateTotal(items);
displayTotal(total);
Total: $70
Function Composition
Function composition combines small functions so the result of one becomes the input of another.
function trimText(value) {
return value.trim();
}
function toLowerCase(value) {
return value.toLowerCase();
}
function replaceSpaces(value) {
return value.replace(
/\s+/g,
"-"
);
}
function createSlug(title) {
return replaceSpaces(
toLowerCase(
trimText(title)
)
);
}
console.log(
createSlug(
" JavaScript Functions "
)
);
javascript-functions
Reusable pipe() Function
A pipeline applies functions from left to right.
function pipe(
...functions
) {
return initialValue =>
functions.reduce(
(value, currentFunction) =>
currentFunction(value),
initialValue
);
}
const createSlug =
pipe(
value =>
value.trim(),
value =>
value.toLowerCase(),
value =>
value.replace(
/\s+/g,
"-"
)
);
console.log(
createSlug(
" JavaScript Closures "
)
);
javascript-closures
Partial Application
Partial application creates a new function by pre-filling some arguments of another function.
function calculatePrice(
price,
taxRate
) {
return (
price +
price * taxRate
);
}
function withTaxRate(
taxRate
) {
return price =>
calculatePrice(
price,
taxRate
);
}
const withTwentyPercentTax =
withTaxRate(0.2);
console.log(
withTwentyPercentTax(
100
)
);
120
Memoization
Memoization stores previous results so repeated calls with the same input can return a cached value.
function memoize(
callback
) {
const cache =
new Map();
return function (value) {
if (
cache.has(value)
) {
return cache.get(
value
);
}
const result =
callback(value);
cache.set(
value,
result
);
return result;
};
}
const square =
memoize(
number => {
console.log(
"Calculating"
);
return (
number * number
);
}
);
console.log(square(5));
console.log(square(5));
Calculating
25
25
Caches consume memory and require reliable cache keys. Memoization works best for pure, expensive functions that receive a limited set of repeated inputs.
Recursion
Recursion occurs when a function calls itself. Every recursive solution needs a base case that eventually stops further calls.
function countdown(
number
) {
if (number <= 0) {
console.log("Done");
return;
}
console.log(number);
countdown(
number - 1
);
}
countdown(3);
3
2
1
Done
Recursive Factorial
function factorial(
number
) {
if (
!Number.isInteger(
number
) ||
number < 0
) {
throw new TypeError(
"number must be a non-negative integer."
);
}
if (number <= 1) {
return 1;
}
return (
number *
factorial(
number - 1
)
);
}
console.log(
factorial(5)
);
120
Recursive Array Processing
function flatten(
values
) {
const result = [];
for (
const value of values
) {
if (
Array.isArray(value)
) {
result.push(
...flatten(value)
);
} else {
result.push(value);
}
}
return result;
}
console.log(
flatten([
1,
[2, [3, 4]],
5
])
);
[1, 2, 3, 4, 5]
Recursion Requirements
| Requirement | Purpose |
|---|---|
| Base case | Stops recursion when a condition is reached. |
| Recursive step | Calls the function again with a smaller or simpler problem. |
| Progress | Ensures every call moves toward the base case. |
| Return propagation | Passes calculated values back through the call stack. |
Deep or infinite recursion may throw a
RangeError. Iteration is often safer for very large or
predictable sequences.
Recursion vs Iteration
| Consideration | Recursion | Iteration |
|---|---|---|
| Nested data | Often natural and expressive. | May require an explicit stack. |
| Large linear sequences | May exhaust the call stack. | Usually safer. |
| State tracking | Stored in function calls. | Stored in variables. |
| Readability | Clear for recursive structures. | Clear for repetitive linear work. |
Immediately Invoked Function Expressions
An immediately invoked function expression, or IIFE, runs as soon as it is created.
(function () {
const message =
"IIFE executed";
console.log(message);
})();
IIFE executed
Arrow Function IIFE
(() => {
const environment =
"development";
console.log(
environment
);
})();
development
IIFE with Arguments and a Return Value
const total =
(function (
price,
quantity
) {
return (
price * quantity
);
})(25, 4);
console.log(total);
100
Why IIFEs Were Common
Before JavaScript modules and block-scoped declarations became widely available, IIFEs were frequently used to create private scopes and avoid global variable conflicts.
| Use Case | Modern Alternative |
|---|---|
| Avoid global variables | JavaScript modules and block scope. |
| Run initialization once | A normal initialization function or module code. |
| Create private state | Closures, modules, or private class fields. |
Use await in older script structures |
An async IIFE or top-level await in supported modules. |
Async IIFE Pattern
(async () => {
try {
const value =
await Promise.resolve(
"Data loaded"
);
console.log(value);
} catch (error) {
console.error(
error.message
);
}
})();
Data loaded
Common Callback and Closure Mistakes
- Calling a callback immediately instead of passing the function.
- Assuming a timer callback runs at an exact time.
- Creating deeply nested callbacks that are difficult to follow.
- Capturing unnecessary large objects inside long-lived closures.
- Using
varin loops with delayed callbacks. - Assuming a function is pure while it mutates arguments or external state.
- Memoizing functions that depend on changing external state.
- Using recursion without a reachable base case.
- Using deep recursion for very large linear tasks.
- Using an IIFE where a normal block or module would be clearer.
- Creating complex functional abstractions for simple operations.
Function Pattern Quick Reference
| Pattern | Purpose |
|---|---|
| Callback | Pass behavior to another function. |
| Higher-order function | Receive or return functions. |
| Closure | Preserve access to lexical variables. |
| Function factory | Create customized functions. |
| Pure function | Calculate predictable results without side effects. |
| Function composition | Combine several focused transformations. |
| Partial application | Create a function with pre-filled arguments. |
| Memoization | Cache results for repeated inputs. |
| Recursion | Solve a problem by calling the same function with a smaller problem. |
| IIFE | Create and execute a function immediately. |
Use callbacks and higher-order functions when behavior genuinely needs to be configurable. Keep pure calculations separate from external effects, use closures for focused state management, and prefer the simplest pattern that clearly solves the problem.
Callbacks, Closures and Functional Patterns Summary
- JavaScript functions are first-class values.
- Callbacks pass behavior into another function.
- Higher-order functions receive or return functions.
- Closures preserve access to variables from their defining scope.
- Each closure can maintain independent private state.
- Pure functions produce predictable results without observable side effects.
- Side effects should be intentional and clearly separated from calculations.
- Function composition combines small reusable transformations.
- Partial application creates specialized functions with pre-filled arguments.
- Memoization caches results for repeated inputs.
- Recursive functions require a reachable base case.
- Very deep recursion may exceed the call stack.
- IIFEs create and execute functions immediately.
- Modern modules and block scope replace many historical IIFE use cases.
JavaScript this, call(), apply(), bind() and Object Creation
The JavaScript this keyword refers to a value determined by
how a function is called. It is not normally determined by where the
function was written.
Traditional functions can receive different this values
depending on their invocation. The call(),
apply(), and bind() methods provide explicit
control over that function context.
Understanding this
In a normal function, the value of this depends on the call
site—the expression used to invoke the function.
| Invocation Style | Typical this Value |
|---|---|
object.method() |
The object before the dot. |
function.call(value) |
The explicitly supplied value. |
function.apply(value) |
The explicitly supplied value. |
new Constructor() |
The newly created instance. |
| Arrow function | Inherited lexically from the surrounding scope. |
| Plain function call in strict mode | undefined |
this in an Object Method
When a traditional function is called as an object method,
this refers to the object used for that call.
const user = {
name: "Alice",
greet() {
return `Hello, ${this.name}!`;
}
};
console.log(
user.greet()
);
Hello, Alice!
The Call Site Determines this
The same function can produce different results when called through different objects.
function introduce() {
return `I am ${this.name}.`;
}
const firstUser = {
name: "Alice",
introduce
};
const secondUser = {
name: "Bob",
introduce
};
console.log(
firstUser.introduce()
);
console.log(
secondUser.introduce()
);
I am Alice.
I am Bob.
Nested Objects and this
this refers to the immediate object used to call the method,
not automatically to the outermost object.
const company = {
name: "Tech Corp",
department: {
name: "Engineering",
getName() {
return this.name;
}
}
};
console.log(
company.department
.getName()
);
Engineering
Losing Method Context
Extracting a method into a standalone variable removes the original method call site.
"use strict";
const user = {
name: "Alice",
greet() {
return `Hello, ${this.name}!`;
}
};
const detachedGreet =
user.greet;
console.log(
detachedGreet()
);
In strict mode, this is undefined during the
standalone call. Reading this.name therefore throws a
TypeError.
Lost Context in Callbacks
Passing an unbound method as a callback can also remove its original object context.
const user = {
name: "Alice",
greet() {
console.log(
`Hello, ${this.name}!`
);
}
};
// Context may be lost
setTimeout(
user.greet,
100
);
Preserving Context with a Wrapper
An arrow callback can call the method through its object when the callback executes.
const user = {
name: "Alice",
greet() {
console.log(
`Hello, ${this.name}!`
);
}
};
setTimeout(
() => {
user.greet();
},
100
);
Hello, Alice!
Arrow Functions and Lexical this
Arrow functions do not create their own this. They use the
this value from the surrounding lexical scope.
const user = {
name: "Alice",
greetLater() {
setTimeout(
() => {
console.log(
`Hello, ${this.name}!`
);
},
100
);
}
};
user.greetLater();
Hello, Alice!
Arrow Function as an Object Method
const user = {
name: "Alice",
greet: () => {
return `Hello, ${this.name}!`;
}
};
The arrow function does not receive user as its
this value. Use method shorthand or a traditional function
when dynamic method context is required.
call()
The call() method invokes a function immediately with an
explicit this value. Additional arguments are supplied
individually.
function greet(
greeting,
punctuation
) {
return (
`${greeting}, ` +
`${this.name}${punctuation}`
);
}
const user = {
name: "Alice"
};
console.log(
greet.call(
user,
"Hello",
"!"
)
);
Hello, Alice!
Reusing a Function with call()
function describe() {
return (
`${this.name} costs ` +
`$${this.price}.`
);
}
const keyboard = {
name: "Keyboard",
price: 80
};
const mouse = {
name: "Mouse",
price: 40
};
console.log(
describe.call(keyboard)
);
console.log(
describe.call(mouse)
);
Keyboard costs $80.
Mouse costs $40.
apply()
The apply() method also invokes a function immediately with an
explicit this value. Its function arguments are supplied in an
array or array-like value.
function greet(
greeting,
punctuation
) {
return (
`${greeting}, ` +
`${this.name}${punctuation}`
);
}
const user = {
name: "Alice"
};
const argumentsList = [
"Welcome",
"!"
];
console.log(
greet.apply(
user,
argumentsList
)
);
Welcome, Alice!
call() vs apply()
| Method | Execution | Arguments |
|---|---|---|
call() |
Invokes immediately | Passed individually |
apply() |
Invokes immediately | Passed as one array-like value |
bind() |
Does not invoke immediately | Can pre-fill individual arguments |
Spread Syntax as an apply() Alternative
Modern code can often use spread syntax instead of
apply() when only argument expansion is required.
const numbers = [
12,
5,
87,
24
];
console.log(
Math.max(...numbers)
);
console.log(
Math.max.apply(
null,
numbers
)
);
87
87
bind()
The bind() method creates a new function with a fixed
this value. It does not execute the original function
immediately.
function greet() {
return `Hello, ${this.name}!`;
}
const user = {
name: "Alice"
};
const boundGreet =
greet.bind(user);
console.log(
boundGreet()
);
Hello, Alice!
Fixing a Detached Method with bind()
const user = {
name: "Alice",
greet() {
return `Hello, ${this.name}!`;
}
};
const detachedGreet =
user.greet;
const boundGreet =
user.greet.bind(user);
console.log(
boundGreet()
);
Hello, Alice!
Binding a Timer Callback
const user = {
name: "Alice",
greet() {
console.log(
`Hello, ${this.name}!`
);
}
};
setTimeout(
user.greet.bind(user),
100
);
Hello, Alice!
Partial Application with bind()
Arguments supplied to bind() are placed before arguments
supplied to the returned function.
function multiply(
first,
second
) {
return first * second;
}
const double =
multiply.bind(
null,
2
);
const triple =
multiply.bind(
null,
3
);
console.log(
double(10)
);
console.log(
triple(10)
);
20
30
A Bound Function Cannot Be Rebound
Calling bind() again does not replace the
this value already fixed on a bound function.
function getName() {
return this.name;
}
const firstUser = {
name: "Alice"
};
const secondUser = {
name: "Bob"
};
const firstBinding =
getName.bind(
firstUser
);
const secondBinding =
firstBinding.bind(
secondUser
);
console.log(
secondBinding()
);
Alice
Every call to bind() creates a new function. When adding and
removing event listeners, store the bound function so the same reference
can be removed later.
const boundHandler =
object.handleClick.bind(
object
);
button.addEventListener(
"click",
boundHandler
);
button.removeEventListener(
"click",
boundHandler
);
call(), apply() and bind() Quick Comparison
| Method | Runs Immediately? | Argument Format | Returns |
|---|---|---|---|
call() |
Yes | Separate arguments | Function result |
apply() |
Yes | Array or array-like argument list | Function result |
bind() |
No | Optional pre-filled arguments | New bound function |
Method Borrowing
A method can be called with another compatible object by using
call(), apply(), or bind().
const user = {
firstName: "Alice",
lastName: "Smith",
getFullName() {
return (
`${this.firstName} ` +
this.lastName
);
}
};
const administrator = {
firstName: "Maya",
lastName: "Jones"
};
console.log(
user.getFullName.call(
administrator
)
);
Maya Jones
Borrowing Array Methods
Some array methods can operate on array-like objects when called with an explicit context.
const arrayLike = {
0: "JavaScript",
1: "Python",
2: "SQL",
length: 3
};
const values =
Array.prototype.slice.call(
arrayLike
);
console.log(values);
console.log(
Array.isArray(values)
);
["JavaScript", "Python", "SQL"]
true
Array.from(arrayLike) is generally clearer when converting
an array-like value into a real array.
Function Properties
Functions are objects and have properties such as name and
length. Custom properties can also be assigned.
function calculateTotal(
price,
quantity,
taxRate = 0.2
) {
const subtotal =
price * quantity;
return (
subtotal +
subtotal * taxRate
);
}
console.log(
calculateTotal.name
);
console.log(
calculateTotal.length
);
calculateTotal
2
The length property counts parameters before the first
default parameter. Rest parameters are not counted.
Custom Function Properties
function formatPrice(
price
) {
return `$${price.toFixed(2)}`;
}
formatPrice.description =
"Formats a USD price.";
formatPrice.version =
"1.0";
console.log(
formatPrice(25)
);
console.log(
formatPrice.description
);
console.log(
formatPrice.version
);
$25.00
Formats a USD price.
1.0
Constructor Functions
A constructor function is a traditional function designed to create objects
with the new operator. Constructor names conventionally begin
with an uppercase letter.
function User(
name,
age
) {
this.name = name;
this.age = age;
this.active = true;
}
const alice =
new User(
"Alice",
30
);
console.log(alice);
User {
name: "Alice",
age: 30,
active: true
}
What new Does
Calling a constructable function with new performs several
connected operations.
| Step | Behavior |
|---|---|
| 1 | Creates a new empty object. |
| 2 | Links the new object's prototype to the constructor's prototype object. |
| 3 | Calls the constructor with this set to the new object. |
| 4 | Returns the new object unless the constructor explicitly returns another object. |
Adding Shared Prototype Methods
Methods placed on the constructor's prototype are shared by every instance instead of being recreated for each object.
function User(name) {
this.name = name;
}
User.prototype.greet =
function () {
return `Hello, ${this.name}!`;
};
const alice =
new User("Alice");
const bob =
new User("Bob");
console.log(
alice.greet()
);
console.log(
bob.greet()
);
console.log(
alice.greet ===
bob.greet
);
Hello, Alice!
Hello, Bob!
true
Checking Constructor Instances
function User(name) {
this.name = name;
}
const alice =
new User("Alice");
console.log(
alice instanceof User
);
console.log(
alice instanceof Object
);
true
true
Forgetting new
"use strict";
function User(name) {
this.name = name;
}
const user =
User("Alice");
In strict mode, calling the constructor without new gives the
function an undefined this value, causing the
property assignment to throw a TypeError.
Protecting a Constructor
A constructor can detect whether it was called with new by
checking new.target.
function User(name) {
if (!new.target) {
return new User(name);
}
this.name = name;
}
const alice =
User("Alice");
console.log(
alice instanceof User
);
console.log(alice.name);
true
Alice
Constructor Return Behavior
Returning a primitive from a constructor is normally ignored. Returning an object explicitly replaces the newly created instance.
function Product(name) {
this.name = name;
return {
replacement: true
};
}
const product =
new Product(
"Keyboard"
);
console.log(product);
{
replacement: true
}
Arrow Functions Cannot Be Constructors
const User = name => {
this.name = name;
};
const alice =
new User("Alice");
// TypeError
Arrow functions do not have constructor behavior or a constructable
prototype property.
Factory Functions
A factory function creates and returns an object without requiring
new.
function createUser(
name,
age
) {
return {
name,
age,
active: true,
greet() {
return (
`Hello, ${this.name}!`
);
}
};
}
const alice =
createUser(
"Alice",
30
);
console.log(
alice.greet()
);
Hello, Alice!
Factory Function with Closure State
function createCounter(
initialValue = 0
) {
let count =
initialValue;
return {
increment() {
count += 1;
return count;
},
decrement() {
count -= 1;
return count;
},
getValue() {
return count;
}
};
}
const counter =
createCounter(10);
counter.increment();
counter.increment();
counter.decrement();
console.log(
counter.getValue()
);
11
Constructor vs Factory Function
| Feature | Constructor Function | Factory Function |
|---|---|---|
| Invocation | new User() |
createUser() |
Uses dynamic this |
Usually yes | Not required |
| Prototype sharing | Built into constructor instances | Must be designed explicitly |
| Private closure state | Possible, but less direct | Natural and common |
Risk of forgetting new |
Yes | No |
instanceof |
Works with the prototype chain | Not automatically tied to the factory |
| Modern alternative | Class syntax | Object composition and closures |
Constructor Function vs Class Syntax
JavaScript classes provide a clearer syntax over the same prototype-based object model.
class User {
constructor(name) {
this.name = name;
}
greet() {
return `Hello, ${this.name}!`;
}
}
const alice =
new User("Alice");
console.log(
alice.greet()
);
Hello, Alice!
Use classes or constructor functions when shared prototype behavior and instance identity are important. Use factory functions when composition, flexible return values, or closure-based private state makes the design clearer.
Common this and Binding Mistakes
- Assuming
thisis determined only by where a function is defined. - Using an arrow function as a method that needs dynamic
this. - Passing an unbound object method as a callback.
- Calling a callback method without preserving its object context.
- Expecting
call()orapply()to return a bound function. - Expecting
bind()to execute the function immediately. - Calling
bind()repeatedly without storing the returned function. - Trying to replace the context of an already bound function.
- Forgetting
newwhen using a constructor function. - Trying to use an arrow function as a constructor.
- Creating instance methods inside a constructor when they could be shared through the prototype.
- Using constructors when a simple object literal or factory would be clearer.
Context and Object Creation Quick Reference
| Goal | Recommended Pattern |
|---|---|
| Call a method with its object | object.method() |
| Invoke with an explicit context | function.call(context, arg) |
| Invoke with an argument array | function.apply(context, args) |
| Create a context-bound function | function.bind(context) |
| Preserve a method inside a callback | Use a wrapper arrow or a stored bound function. |
| Pre-fill function arguments | function.bind(null, firstArgument) |
| Create constructor instances | new Constructor() |
| Share instance methods | Place methods on the constructor prototype or use a class. |
| Create objects without new | Use a factory function. |
| Maintain private closure state | Return methods from a factory function. |
Determine this from the call site, use method shorthand for
object methods, use arrow functions when lexical context is required, and
bind methods only when they must be passed independently. Choose factory,
constructor, or class patterns according to the object's actual behavior
and state requirements.
Function Context and Object Creation Summary
- Traditional function
thisvalues usually depend on the call site. - Method calls assign
thisto the object before the dot. - Detached methods lose their original object context.
- Arrow functions inherit
thisfrom their surrounding lexical scope. call()invokes a function with an explicit context and separate arguments.apply()invokes a function with an explicit context and an argument list.bind()creates a new function with fixed context and optional arguments.- Methods can be borrowed by calling them with another compatible object.
- Functions are objects with properties such as
nameandlength. - Constructor functions create instances when called with
new. - Prototype methods are shared between constructor instances.
- Arrow functions cannot be used as constructors.
- Factory functions create and return objects without
new. - Factory functions can use closures to maintain private state.
- Classes provide modern syntax for constructor and prototype behavior.
JavaScript Generator Functions
Generator functions pause and resume execution. Declare one with
function*, produce values with yield, and
consume the returned iterator with next() or
for...of.
function* createIds() {
let id = 1;
while (true) {
yield id++;
}
}
const ids = createIds();
console.log(ids.next().value); // 1
console.log(ids.next().value); // 2
| Feature | Purpose |
|---|---|
function* | Declares a generator function. |
yield value | Pauses execution and returns a value. |
iterator.next() | Resumes execution and returns { value, done }. |
yield* | Delegates to another iterable. |
JavaScript Conditionals
JavaScript conditionals control which parts of a program execute based on whether an expression is true or false. They allow applications to react to user input, validate data, control access, display different content, and make decisions during execution.
The main conditional tools are if, else,
else if, the ternary operator, and
switch. These structures rely on comparison operators,
logical operators, and JavaScript's truthy and falsy value rules.
Why Use Conditional Statements?
| Use Case | Example |
|---|---|
| Validate user input | Check whether an email address or password is valid. |
| Control access | Allow administrators to access protected features. |
| Display different content | Show a login button or an account menu. |
| Handle errors | Respond when requested data is missing. |
| Apply business rules | Calculate discounts based on membership level. |
| React to application state | Show a loading indicator while data is being fetched. |
The if Statement
An if statement executes its code block only when its
condition evaluates to a truthy value.
const age = 20;
if (age >= 18) {
console.log(
"Access granted"
);
}
Access granted
if Statement Structure
| Part | Example | Purpose |
|---|---|---|
| Keyword | if |
Starts the conditional statement. |
| Condition | (age >= 18) |
Evaluates to a truthy or falsy value. |
| Code block | { ... } |
Runs only when the condition is truthy. |
When the Condition Is False
If the condition evaluates to a falsy value, JavaScript skips the complete
if block and continues with the next statement.
const age = 16;
console.log(
"Checking access"
);
if (age >= 18) {
console.log(
"Access granted"
);
}
console.log(
"Check complete"
);
Checking access
Check complete
Conditions Produce Boolean-Like Decisions
Comparison expressions commonly produce the Boolean values
true or false.
const score = 85;
const hasPassed =
score >= 60;
console.log(hasPassed);
if (hasPassed) {
console.log(
"You passed"
);
}
true
You passed
Using a Boolean Variable
A Boolean variable can be used directly as an
if condition. Comparing it to true is usually
unnecessary.
const isLoggedIn = true;
if (isLoggedIn) {
console.log(
"Welcome back"
);
}
Welcome back
// Recommended
if (isLoggedIn) {
// ...
}
// Usually unnecessary
if (isLoggedIn === true) {
// ...
}
Checking for false with the NOT Operator
The logical NOT operator ! reverses a value's truthiness.
It is commonly used to execute code when a Boolean value is false.
const isLoggedIn = false;
if (!isLoggedIn) {
console.log(
"Please log in"
);
}
Please log in
Comparison Operators in Conditions
Comparison operators compare two values and return a Boolean result.
| Operator | Meaning | Example | Result |
|---|---|---|---|
=== |
Strict equality | 5 === 5 |
true |
!== |
Strict inequality | 5 !== "5" |
true |
> |
Greater than | 10 > 5 |
true |
< |
Less than | 3 < 8 |
true |
>= |
Greater than or equal to | 18 >= 18 |
true |
<= |
Less than or equal to | 4 <= 3 |
false |
== |
Loose equality with coercion | 5 == "5" |
true |
!= |
Loose inequality with coercion | 5 != "5" |
false |
Strict Equality
The strict equality operator === compares both value and data
type without converting either operand.
const enteredCode =
"1234";
if (
enteredCode === "1234"
) {
console.log(
"Correct code"
);
}
console.log(
5 === 5
);
console.log(
5 === "5"
);
Correct code
true
false
Loose Equality
The loose equality operator == may convert one or both values
before comparing them. This can produce surprising results.
console.log(
5 == "5"
);
console.log(
false == 0
);
console.log(
"" == 0
);
console.log(
null == undefined
);
true
true
true
true
Use === and !== by default. They make type
differences visible and avoid most equality coercion surprises.
Strict Inequality
The strict inequality operator !== returns true when the
values or their types differ.
const status =
"pending";
if (
status !== "complete"
) {
console.log(
"Work remains"
);
}
console.log(
10 !== "10"
);
Work remains
true
Numeric Range Conditions
Relational operators are commonly used to check whether a number is above, below, or equal to a boundary.
const score = 75;
const passingScore = 60;
if (
score >= passingScore
) {
console.log(
"Passing score"
);
}
Passing score
String Comparisons
Strings are compared according to Unicode code-unit order. Comparisons are case-sensitive.
const role = "Admin";
console.log(
role === "Admin"
);
console.log(
role === "admin"
);
if (
role.toLowerCase() ===
"admin"
) {
console.log(
"Administrator detected"
);
}
true
false
Administrator detected
Normalize Before Comparing User Input
User-entered text often contains inconsistent capitalization or whitespace. Normalize the value before comparing it.
const enteredAnswer =
" JAVASCRIPT ";
const normalizedAnswer =
enteredAnswer
.trim()
.toLowerCase();
if (
normalizedAnswer ===
"javascript"
) {
console.log(
"Correct answer"
);
}
Correct answer
Comparing Dates
Relational operators can compare Date objects by their numeric timestamps. Two separately created Date objects are not strictly equal, even when they represent the same time.
const startDate =
new Date(
"2026-07-01"
);
const endDate =
new Date(
"2026-07-31"
);
if (
startDate < endDate
) {
console.log(
"Valid date range"
);
}
const firstDate =
new Date(
"2026-07-01"
);
const secondDate =
new Date(
"2026-07-01"
);
console.log(
firstDate === secondDate
);
console.log(
firstDate.getTime() ===
secondDate.getTime()
);
Valid date range
false
true
Objects Compare by Reference
Strict equality compares object references rather than property contents.
const firstUser = {
name: "Alice"
};
const secondUser = {
name: "Alice"
};
const sameUser =
firstUser;
console.log(
firstUser === secondUser
);
console.log(
firstUser === sameUser
);
false
true
Block Scope Inside if
Variables declared with let or const inside an
if block are available only inside that block.
const isAdmin = true;
if (isAdmin) {
const message =
"Admin access";
console.log(message);
}
// ReferenceError
// console.log(message);
Admin access
Declare Shared Results Outside the Block
When a value must be available after a conditional block, declare the variable in the surrounding scope.
const score = 85;
let result;
if (score >= 60) {
result = "Passed";
}
console.log(result);
Passed
Braces Around Conditional Blocks
JavaScript permits a single statement without braces, but using braces is safer and easier to maintain.
const isActive = true;
if (isActive)
console.log("Active");
console.log(
"This always runs"
);
Only the first statement belongs to the condition. Indentation does not control JavaScript execution.
const isActive = true;
if (isActive) {
console.log(
"Active"
);
console.log(
"Condition confirmed"
);
}
Active
Condition confirmed
Assignment vs Comparison
let isActive = false;
// Assignment, not comparison
if (isActive = true) {
console.log(
"This block runs"
);
}
console.log(isActive);
// true
The assignment operator = changes the variable and produces
the assigned value. Use === when comparing values.
Calling Functions Inside Conditions
A function that returns a Boolean value can be used directly as an
if condition.
function isValidAge(
age
) {
return (
Number.isInteger(age) &&
age >= 18
);
}
const age = 30;
if (isValidAge(age)) {
console.log(
"Valid adult age"
);
}
Valid adult age
Boolean Function Naming
Functions returning Boolean values often begin with words such as
is, has, can, or
should.
| Function Name | Meaning |
|---|---|
isValidEmail() |
Checks whether an email value is valid. |
hasPermission() |
Checks whether a permission exists. |
canEdit() |
Checks whether editing is allowed. |
shouldRefresh() |
Checks whether a refresh should occur. |
Basic if Statement Mistakes
- Using
=when a comparison was intended. - Using loose equality when strict equality would be clearer.
- Comparing objects by content with
===. - Forgetting that string comparisons are case-sensitive.
- Omitting braces around conditional code blocks.
- Trying to access a block-scoped variable outside the block.
- Repeating expensive function calls inside the same condition.
- Writing conditions whose meaning is unclear from the variable names.
Use clear Boolean expressions, prefer strict equality, normalize user input before comparing it, include braces around conditional blocks, and use descriptive Boolean variable and function names.
Conditionals Block 1A-1 Summary
- Conditional statements control which code executes.
- An
ifblock runs when its condition is truthy. - A falsy condition causes the block to be skipped.
- Comparison operators produce Boolean results.
- Use
===and!==by default. - Loose equality performs type coercion and can be surprising.
- String comparisons are case-sensitive.
- Objects and arrays compare by reference.
- Date values can be compared through their timestamps.
letandconstare block-scoped.- Braces make conditional code safer and clearer.
- Boolean-returning functions can be used directly in conditions.
JavaScript else and else if Statements
The else statement provides an alternative block when an
if condition is falsy. The else if statement
adds more possible conditions, allowing a program to choose between
several outcomes.
JavaScript evaluates conditional branches from top to bottom. As soon as one condition is truthy, its block runs and the remaining branches in that chain are skipped.
The else Statement
An else block runs only when the preceding
if condition is falsy.
const age = 16;
if (age >= 18) {
console.log(
"Access granted"
);
} else {
console.log(
"Access denied"
);
}
Access denied
Exactly One Branch Runs
In a basic if...else statement, either the
if block or the else block runs. Both blocks
cannot run during the same evaluation.
const isOnline = true;
if (isOnline) {
console.log(
"User is online"
);
} else {
console.log(
"User is offline"
);
}
User is online
if...else Execution Flow
| Condition Result | if Block | else Block |
|---|---|---|
| Truthy | Runs | Skipped |
| Falsy | Skipped | Runs |
Assigning a Result with if...else
Declare a variable in the surrounding scope when both branches need to assign its value.
const score = 72;
let result;
if (score >= 60) {
result = "Passed";
} else {
result = "Failed";
}
console.log(result);
Passed
Returning from if...else Branches
A function can return a different result from each branch.
function getAccessMessage(
isLoggedIn
) {
if (isLoggedIn) {
return "Welcome back";
} else {
return "Please log in";
}
}
console.log(
getAccessMessage(true)
);
console.log(
getAccessMessage(false)
);
Welcome back
Please log in
When the if branch returns, the remaining function code runs
only when that condition was false. The else can therefore
often be removed.
function getAccessMessage(
isLoggedIn
) {
if (isLoggedIn) {
return "Welcome back";
}
return "Please log in";
}
The else if Statement
Use else if when more than two outcomes are possible.
const score = 82;
if (score >= 90) {
console.log("Grade A");
} else if (score >= 80) {
console.log("Grade B");
} else if (score >= 70) {
console.log("Grade C");
} else {
console.log(
"Needs improvement"
);
}
Grade B
Conditional Chains Stop at the First Match
Once a truthy branch is found, JavaScript runs that block and skips every later branch in the same chain.
const temperature = 32;
if (temperature >= 30) {
console.log("Hot");
} else if (
temperature >= 20
) {
console.log("Warm");
} else if (
temperature >= 10
) {
console.log("Cool");
} else {
console.log("Cold");
}
Hot
Branch Order Matters
Place more specific or higher-threshold conditions before broader conditions that would also match the same value.
const score = 95;
if (score >= 60) {
console.log("Passed");
} else if (score >= 90) {
console.log("Excellent");
}
A score of 95 matches the first condition, so the
Excellent branch is never reached.
const score = 95;
if (score >= 90) {
console.log("Excellent");
} else if (score >= 60) {
console.log("Passed");
} else {
console.log("Failed");
}
Excellent
Grading Example
Validate the acceptable range before assigning a grade.
function getGrade(score) {
if (
!Number.isFinite(score) ||
score < 0 ||
score > 100
) {
return "Invalid score";
}
if (score >= 90) {
return "A";
} else if (score >= 80) {
return "B";
} else if (score >= 70) {
return "C";
} else if (score >= 60) {
return "D";
} else {
return "F";
}
}
console.log(
getGrade(94)
);
console.log(
getGrade(73)
);
console.log(
getGrade(45)
);
console.log(
getGrade(120)
);
A
C
F
Invalid score
Removing Unnecessary else Statements
When every successful branch returns, an else if chain can
often be written as a sequence of independent guard conditions.
function getGrade(score) {
if (
!Number.isFinite(score) ||
score < 0 ||
score > 100
) {
return "Invalid score";
}
if (score >= 90) {
return "A";
}
if (score >= 80) {
return "B";
}
if (score >= 70) {
return "C";
}
if (score >= 60) {
return "D";
}
return "F";
}
console.log(
getGrade(82)
);
B
Independent if Statements vs else if
Separate if statements may all run. An
if...else if chain selects only the first matching branch.
const number = 12;
if (number > 0) {
console.log("Positive");
}
if (number % 2 === 0) {
console.log("Even");
}
if (number > 10) {
console.log(
"Greater than ten"
);
}
Positive
Even
Greater than ten
const number = 12;
if (number < 0) {
console.log("Negative");
} else if (number === 0) {
console.log("Zero");
} else {
console.log("Positive");
}
Positive
Choosing Between Separate if Statements and a Chain
| Situation | Recommended Structure |
|---|---|
| Several conditions may all apply | Use separate if statements. |
| Only one outcome should be selected | Use an if...else if...else chain. |
| Invalid input should stop processing | Use a guard clause with an early return or throw. |
| A simple two-way decision is required | Use if...else. |
Checking Account Status
function getStatusMessage(
status
) {
if (status === "active") {
return "Account active";
} else if (
status === "pending"
) {
return (
"Account awaiting approval"
);
} else if (
status === "suspended"
) {
return "Account suspended";
} else {
return "Unknown status";
}
}
console.log(
getStatusMessage(
"pending"
)
);
Account awaiting approval
Normalizing Before Branching
Normalize user input once before checking multiple possible values.
function getStatusMessage(
status
) {
if (
typeof status !==
"string"
) {
return "Invalid status";
}
const normalizedStatus =
status
.trim()
.toLowerCase();
if (
normalizedStatus ===
"active"
) {
return "Account active";
} else if (
normalizedStatus ===
"pending"
) {
return "Account pending";
} else {
return "Unknown status";
}
}
console.log(
getStatusMessage(
" ACTIVE "
)
);
Account active
Price-Based Discount Example
function getDiscountRate(
orderTotal
) {
if (
!Number.isFinite(
orderTotal
) ||
orderTotal < 0
) {
return 0;
}
if (orderTotal >= 500) {
return 0.2;
} else if (
orderTotal >= 250
) {
return 0.1;
} else if (
orderTotal >= 100
) {
return 0.05;
} else {
return 0;
}
}
const orderTotal = 300;
const discountRate =
getDiscountRate(
orderTotal
);
const discount =
orderTotal *
discountRate;
console.log(
discountRate
);
console.log(discount);
0.1
30
Boundary Conditions
Decide carefully whether a boundary value should be included with
>= or excluded with >.
function getTicketType(
age
) {
if (age < 0) {
return "Invalid age";
} else if (age < 13) {
return "Child";
} else if (age < 18) {
return "Teen";
} else if (age >= 65) {
return "Senior";
} else {
return "Adult";
}
}
console.log(
getTicketType(12)
);
console.log(
getTicketType(13)
);
console.log(
getTicketType(65)
);
Child
Teen
Senior
Overlapping Conditions
Conditions overlap when more than one expression could be true for the same
value. In an else if chain, only the first matching condition
matters.
const value = 15;
if (value > 10) {
console.log(
"Greater than ten"
);
} else if (value > 5) {
console.log(
"Greater than five"
);
}
Both comparisons are true for 15, but only the first branch
runs. Order conditions from most specific to most general.
Unreachable Conditional Branches
A branch becomes unreachable when an earlier condition already covers all values that could satisfy it.
const age = 25;
if (age >= 18) {
console.log("Adult");
} else if (age >= 21) {
console.log(
"At least twenty-one"
);
}
Every age of at least 21 also satisfies
age >= 18. The second branch can never run.
Using an else Fallback
The final else handles every value not matched by the earlier
conditions.
function getThemeMessage(
theme
) {
if (theme === "light") {
return "Light theme";
} else if (
theme === "dark"
) {
return "Dark theme";
} else if (
theme === "system"
) {
return "System theme";
} else {
return (
"Unsupported theme"
);
}
}
console.log(
getThemeMessage(
"blue"
)
);
Unsupported theme
When No Final else Is Needed
A final else is optional. Omit it when unmatched values should
intentionally produce no action.
const notificationType =
"success";
if (
notificationType ===
"success"
) {
console.log(
"Show success message"
);
} else if (
notificationType ===
"error"
) {
console.log(
"Show error message"
);
}
console.log(
"Continue application"
);
Show success message
Continue application
Complex Conditions Should Be Named
Store complex comparison logic in descriptively named Boolean variables to make each branch easier to understand.
const user = {
age: 30,
active: true,
verified: true,
role: "editor"
};
const isAdult =
user.age >= 18;
const hasActiveAccount =
user.active &&
user.verified;
const canAccessEditor =
isAdult &&
hasActiveAccount &&
user.role === "editor";
if (canAccessEditor) {
console.log(
"Editor access granted"
);
} else {
console.log(
"Editor access denied"
);
}
Editor access granted
else and else if Common Mistakes
- Placing broad conditions before more specific conditions.
- Creating an unreachable
else ifbranch. - Using an
else ifchain when several conditions should all run. - Using separate
ifstatements when only one result should be selected. - Forgetting to validate values before applying range rules.
- Using the wrong inclusive or exclusive boundary operator.
- Repeating the same normalization or calculation in every branch.
- Adding an unnecessary
elseafter a branch that already returns. - Using a fallback branch that silently hides invalid input.
- Writing long conditional chains with unclear overlapping rules.
Conditional Branch Quick Reference
| Goal | Recommended Structure |
|---|---|
| Run code only when a condition is true | if |
| Choose between two outcomes | if...else |
| Choose the first matching outcome | if...else if...else |
| Allow several conditions to run | Use separate if statements. |
| Handle invalid input first | Use a guard condition and early return. |
| Check numeric thresholds | Order conditions from highest or most specific to lowest. |
| Handle every unmatched value | Add a final else. |
| Make complex conditions readable | Use named Boolean variables. |
Order branches from most specific to most general, validate input before
applying business rules, use separate if statements when
several conditions may apply, and remove unnecessary
else blocks after early returns.
Conditionals Block 1A-2 Summary
elseprovides an alternative when anifcondition is falsy.else ifadds more possible conditional outcomes.- Only the first matching branch in a conditional chain runs.
- Branch order affects the final result.
- Specific conditions should usually appear before broad conditions.
- Separate
ifstatements can all execute. - An
else ifchain selects one branch. - Boundary operators determine whether threshold values are included.
- A final
elsehandles every unmatched value. - Early returns can remove unnecessary nesting and
elseblocks. - Named Boolean variables make complex conditions easier to read.
- Input should be validated before applying conditional business rules.
JavaScript Nested Conditions, Guard Clauses and Early Returns
Conditional logic often becomes more complex when several requirements must be checked in a specific order. Nested conditions place one conditional statement inside another, while guard clauses handle invalid or exceptional cases before the main logic continues.
Early returns, early throws, and named Boolean conditions can reduce nesting, clarify validation flow, and make the successful execution path easier to understand.
Nested Conditional Statements
A nested conditional is an if statement placed inside another
conditional block.
const isLoggedIn = true;
const isAdmin = true;
if (isLoggedIn) {
console.log(
"User authenticated"
);
if (isAdmin) {
console.log(
"Admin access granted"
);
}
}
User authenticated
Admin access granted
When Nested Conditions Are Useful
Nesting is appropriate when an inner check is meaningful only after an outer requirement has already been satisfied.
| Outer Condition | Inner Condition |
|---|---|
| User is authenticated | User has a required role. |
| Form data exists | Individual fields are valid. |
| API response succeeded | Returned data contains expected values. |
| Feature is enabled | User has permission to use it. |
| Object exists | Nested property satisfies a condition. |
Nested if...else
const user = {
loggedIn: true,
verified: false
};
if (user.loggedIn) {
if (user.verified) {
console.log(
"Full access granted"
);
} else {
console.log(
"Verify your account"
);
}
} else {
console.log(
"Please log in"
);
}
Verify your account
The Dangling else Problem
Without braces, an else belongs to the nearest unmatched
if. Braces make the intended structure explicit.
if (isLoggedIn)
if (isAdmin)
console.log(
"Admin"
);
else
console.log(
"Not an admin"
);
The else belongs to if (isAdmin), not
if (isLoggedIn).
Deep Nesting Becomes Difficult to Read
Several nested levels increase indentation and make it harder to identify the main successful path.
function processOrder(
user,
order
) {
if (user) {
if (user.active) {
if (order) {
if (
order.items.length > 0
) {
if (
order.paymentReady
) {
return (
"Order processed"
);
}
}
}
}
}
return "Unable to process";
}
Guard Clauses
A guard clause checks an invalid, exceptional, or completed case near the beginning of a function and exits immediately.
function greetUser(user) {
if (!user) {
return "User unavailable";
}
return `Hello, ${user.name}!`;
}
console.log(
greetUser(null)
);
console.log(
greetUser({
name: "Alice"
})
);
User unavailable
Hello, Alice!
Reducing Nesting with Guard Clauses
function processOrder(
user,
order
) {
if (!user) {
return "User unavailable";
}
if (!user.active) {
return "User inactive";
}
if (!order) {
return "Order unavailable";
}
if (
!Array.isArray(
order.items
) ||
order.items.length === 0
) {
return "Order is empty";
}
if (
!order.paymentReady
) {
return (
"Payment is not ready"
);
}
return "Order processed";
}
console.log(
processOrder(
{
active: true
},
{
items: [
"Keyboard"
],
paymentReady: true
}
)
);
Order processed
Guard Clause Benefits
| Benefit | Description |
|---|---|
| Reduced nesting | Invalid cases exit before the main logic begins. |
| Clear success path | The primary operation remains at the lowest indentation level. |
| Focused validation | Each requirement can have a clear message or error. |
| Easier debugging | The exact failed condition is easier to identify. |
| Safer property access | Values are checked before later code uses them. |
Early Return
An early return ends the current function before reaching its final statement.
function calculateDiscount(
total
) {
if (
!Number.isFinite(total)
) {
return 0;
}
if (total <= 0) {
return 0;
}
if (total >= 500) {
return total * 0.2;
}
if (total >= 250) {
return total * 0.1;
}
return total * 0.05;
}
console.log(
calculateDiscount(600)
);
console.log(
calculateDiscount(-10)
);
120
0
Return Ends Function Execution
function checkValue(
value
) {
if (value < 0) {
return "Negative";
}
console.log(
"Validation passed"
);
return "Valid";
}
console.log(
checkValue(-5)
);
console.log(
checkValue(10)
);
Negative
Validation passed
Valid
Early Throw
Throw an error when invalid input violates the function contract and the function cannot continue meaningfully.
function calculatePrice(
price,
quantity
) {
if (
!Number.isFinite(price)
) {
throw new TypeError(
"price must be a finite number."
);
}
if (
!Number.isInteger(
quantity
) ||
quantity < 0
) {
throw new TypeError(
"quantity must be a non-negative integer."
);
}
return price * quantity;
}
console.log(
calculatePrice(
25,
4
)
);
100
Return vs Throw
| Technique | Typical Use |
|---|---|
| Return a normal result | The outcome is part of expected application flow. |
Return null |
A missing result is expected and should be handled by the caller. |
| Return a result object | Success and failure are both expected outcomes. |
| Throw an error | The function contract is violated or execution cannot continue normally. |
Returning a Result Object
function validateUsername(
username
) {
if (
typeof username !==
"string"
) {
return {
valid: false,
error:
"Username must be text."
};
}
const normalized =
username.trim();
if (!normalized) {
return {
valid: false,
error:
"Username is required."
};
}
if (
normalized.length < 3
) {
return {
valid: false,
error:
"Username is too short."
};
}
return {
valid: true,
value: normalized
};
}
console.log(
validateUsername(
"Alice"
)
);
{
valid: true,
value: "Alice"
}
Validation Order Matters
Validate broad structural requirements before accessing methods or properties that depend on them.
function normalizeEmail(
email
) {
if (
typeof email !==
"string"
) {
return null;
}
const normalized =
email
.trim()
.toLowerCase();
if (!normalized) {
return null;
}
if (
!normalized.includes(
"@"
)
) {
return null;
}
return normalized;
}
console.log(
normalizeEmail(
" ALICE@EXAMPLE.COM "
)
);
alice@example.com
function normalizeEmail(
email
) {
const normalized =
email.trim();
}
This throws when email is null,
undefined, or another value without a
trim() method.
Validate Outer Objects First
function getUserCity(
user
) {
if (
user === null ||
typeof user !==
"object" ||
Array.isArray(user)
) {
return "Unknown";
}
if (
user.profile === null ||
typeof user.profile !==
"object"
) {
return "Unknown";
}
if (
typeof user.profile.city !==
"string"
) {
return "Unknown";
}
const city =
user.profile.city.trim();
return city || "Unknown";
}
console.log(
getUserCity({
profile: {
city: "London"
}
})
);
London
Optional Chaining as a Compact Alternative
Optional chaining can simplify safe property access when detailed error messages are not required.
function getUserCity(
user
) {
const city =
user?.profile?.city;
if (
typeof city !==
"string"
) {
return "Unknown";
}
return (
city.trim() ||
"Unknown"
);
}
console.log(
getUserCity(null)
);
console.log(
getUserCity({
profile: {
city: "London"
}
})
);
Unknown
London
Named Boolean Conditions
Complex conditional expressions become easier to understand when their parts are assigned meaningful names.
function canPublish(
user,
article
) {
const hasValidUser =
user !== null &&
typeof user ===
"object";
if (!hasValidUser) {
return false;
}
const hasPublishingRole =
user.role === "admin" ||
user.role === "editor";
const accountIsReady =
user.active &&
user.verified;
const articleIsReady =
article &&
article.status === "draft" &&
article.title?.trim();
return Boolean(
hasPublishingRole &&
accountIsReady &&
articleIsReady
);
}
console.log(
canPublish(
{
role: "editor",
active: true,
verified: true
},
{
status: "draft",
title: "JavaScript"
}
)
);
true
Positive vs Negative Conditions
Positive conditions are often easier to understand than several layers of negation.
| Less Clear | Clearer Alternative |
|---|---|
if (!isNotReady) |
if (isReady) |
if (!hasNoItems) |
if (hasItems) |
if (!user.isInactive) |
if (user.isActive) |
if (!(value < minimum)) |
if (value >= minimum) |
Avoid Double Negatives
if (
!user.isNotVerified
) {
enableAccess();
}
A positive property such as user.isVerified communicates the
rule more directly.
Extract Complex Rules into Functions
Reusable business rules should often be represented by named functions.
function isEligibleForDiscount(
user,
orderTotal
) {
if (!user?.active) {
return false;
}
if (
!Number.isFinite(
orderTotal
)
) {
return false;
}
const isPremium =
user.membership ===
"premium";
const hasMinimumOrder =
orderTotal >= 100;
return (
isPremium &&
hasMinimumOrder
);
}
const user = {
active: true,
membership: "premium"
};
if (
isEligibleForDiscount(
user,
250
)
) {
console.log(
"Discount available"
);
}
Discount available
Early Continue in Loops
The continue statement skips the remaining work for the
current loop iteration and moves to the next item.
const values = [
10,
null,
20,
"30",
40
];
let total = 0;
for (
const value of values
) {
if (
typeof value !==
"number" ||
!Number.isFinite(value)
) {
continue;
}
total += value;
}
console.log(total);
70
Reducing Loop Nesting with continue
const users = [
{
name: "Alice",
active: true,
verified: true
},
{
name: "Bob",
active: false,
verified: true
},
{
name: "Maya",
active: true,
verified: false
}
];
for (
const user of users
) {
if (!user.active) {
continue;
}
if (!user.verified) {
continue;
}
console.log(
`Processing ${user.name}`
);
}
Processing Alice
Early break in Loops
The break statement ends the nearest loop immediately.
const users = [
{
id: 1,
name: "Alice"
},
{
id: 2,
name: "Bob"
},
{
id: 3,
name: "Maya"
}
];
let foundUser = null;
for (
const user of users
) {
if (user.id === 2) {
foundUser = user;
break;
}
}
console.log(foundUser);
{
id: 2,
name: "Bob"
}
Return, continue and break Comparison
| Statement | Effect |
|---|---|
return |
Ends the current function. |
continue |
Skips the remainder of the current loop iteration. |
break |
Ends the nearest loop or switch statement. |
throw |
Stops normal execution and propagates an error. |
Using Array Methods Instead of Manual Conditions
Array methods can express common conditional collection operations more directly.
| Goal | Array Method |
|---|---|
| Keep matching values | filter() |
| Find the first match | find() |
| Check whether any value matches | some() |
| Check whether every value matches | every() |
| Transform values conditionally | map() |
Conditional Collection Methods
const users = [
{
name: "Alice",
active: true
},
{
name: "Bob",
active: false
},
{
name: "Maya",
active: true
}
];
const activeUsers =
users.filter(
user => user.active
);
const hasInactiveUser =
users.some(
user => !user.active
);
const everyUserIsActive =
users.every(
user => user.active
);
console.log(
activeUsers
);
console.log(
hasInactiveUser
);
console.log(
everyUserIsActive
);
[
{
name: "Alice",
active: true
},
{
name: "Maya",
active: true
}
]
true
false
Lookup Tables Instead of Long Conditional Chains
An object or Map can replace a long chain when exact keys correspond directly to values or handlers.
function getStatusMessage(
status
) {
const messages = {
active:
"Account active",
pending:
"Account pending",
suspended:
"Account suspended"
};
return (
messages[status] ??
"Unknown status"
);
}
console.log(
getStatusMessage(
"pending"
)
);
console.log(
getStatusMessage(
"deleted"
)
);
Account pending
Unknown status
Handler Lookup Pattern
const handlers = {
save() {
return "Saved";
},
delete() {
return "Deleted";
},
archive() {
return "Archived";
}
};
function runAction(action) {
const handler =
handlers[action];
if (!handler) {
return "Unknown action";
}
return handler();
}
console.log(
runAction("save")
);
console.log(
runAction("share")
);
Saved
Unknown action
Conditional Pattern Comparison
| Pattern | Best Used For |
|---|---|
| Nested condition | An inner rule that matters only after an outer condition succeeds. |
| Guard clause | Invalid, exceptional, or completed cases that should exit early. |
| Named Boolean | A complex rule with several meaningful parts. |
| Helper function | A reusable condition or business rule. |
| Lookup object | Exact keys mapped directly to values or handlers. |
| Array method | Conditional processing of collection elements. |
| Early continue | Skipping invalid or irrelevant loop items. |
| Early break | Stopping a search after the required value is found. |
Common Nested Conditional Mistakes
- Creating several nested levels when guard clauses would be clearer.
- Omitting braces in nested conditional structures.
- Validating properties before confirming that their parent object exists.
- Using string or array methods before validating the input type.
- Returning inconsistent data types from related branches.
- Catching every invalid case with one vague fallback message.
- Using negative Boolean names that create double negatives.
- Repeating complex rules instead of extracting named variables or functions.
- Using separate checks when a lookup object would represent exact mappings more clearly.
- Using nested loops and conditions when collection methods would better express the intention.
- Throwing errors for expected normal outcomes.
- Silently returning a fallback when invalid input should be reported.
Guard Clause Quick Reference
| Goal | Pattern |
|---|---|
| Stop when a required value is missing | if (!value) return; |
| Reject invalid input | if (!isValid) throw new TypeError(...); |
| Skip an invalid loop item | if (!isValid) continue; |
| Stop a loop after a match | if (matches) break; |
| Return an expected failure result | return { valid: false, error: "..." }; |
| Protect nested property access | Validate parent values or use optional chaining. |
| Reduce deep indentation | Handle exceptional cases before the success path. |
| Clarify a complex condition | Extract named Boolean variables. |
Use nesting only when conditions are genuinely dependent. Handle invalid and exceptional cases early, validate outer structures before accessing nested data, and keep the main successful execution path as flat and readable as possible.
Conditionals Block 2 Summary
- Nested conditions place one decision inside another.
- Nesting is useful when an inner check depends on an outer condition.
- Deep nesting can hide the main execution path.
- Guard clauses handle invalid or exceptional cases early.
- Early returns end the current function immediately.
- Early throws report invalid function input.
- Validation should proceed from broad structure to specific values.
- Parent objects should be validated before nested properties are accessed.
- Optional chaining can simplify safe property access.
- Named Boolean variables make complex rules easier to understand.
- Positive condition names are usually clearer than double negatives.
continueskips the current loop iteration.breakends the nearest loop.- Array methods can replace many manual conditional collection loops.
- Lookup objects can replace long chains of exact-value comparisons.
JavaScript Loops
JavaScript loops repeat a block of code while a condition remains true or for each value in a collection. They reduce duplicated code and make it possible to process arrays, generate sequences, search for values, validate data, and repeat application tasks.
The main JavaScript loop structures are for,
while, do...while,
for...of, and for...in. JavaScript also
provides array methods such as forEach(),
map(), filter(), and
reduce() for common collection operations.
Why Use Loops?
| Use Case | Example |
|---|---|
| Repeat an operation | Display numbers from 1 to 10. |
| Process array values | Calculate the total price of shopping-cart items. |
| Search for data | Find a user with a specific ID. |
| Validate collections | Check whether every form field is valid. |
| Generate content | Create table rows or interface components. |
| Transform values | Convert product names to uppercase. |
| Repeat until a condition changes | Continue requesting input until it is valid. |
JavaScript Loop Overview
| Loop | Best Used For |
|---|---|
for |
Repeating code with a counter or known iteration structure. |
while |
Repeating while a condition remains truthy. |
do...while |
Running a block at least once before checking its condition. |
for...of |
Iterating over values in arrays, strings, maps, sets, and other iterables. |
for...in |
Iterating over enumerable property keys. |
| Array methods | Performing declarative array transformations, searches, and tests. |
The for Loop
A for loop repeats a block of code using an initializer,
condition, and update expression.
for (
let number = 1;
number <= 5;
number += 1
) {
console.log(number);
}
1
2
3
4
5
Anatomy of a for Loop
| Part | Example | Purpose |
|---|---|---|
| Initializer | let number = 1 |
Runs once before the loop begins. |
| Condition | number <= 5 |
Checked before every iteration. |
| Update expression | number += 1 |
Runs after every completed iteration. |
| Loop body | { console.log(number); } |
Contains the code repeated during each iteration. |
for Loop Execution Order
A for loop follows the same execution sequence during each
iteration.
| Step | Action |
|---|---|
| 1 | Run the initializer once. |
| 2 | Evaluate the loop condition. |
| 3 | Run the loop body when the condition is truthy. |
| 4 | Run the update expression. |
| 5 | Return to the condition and repeat. |
| 6 | Exit when the condition becomes falsy. |
Tracing a for Loop
for (
let index = 0;
index < 3;
index += 1
) {
console.log(
`Iteration ${index}`
);
}
console.log(
"Loop complete"
);
Iteration 0
Iteration 1
Iteration 2
Loop complete
Zero-Based Loop Counters
Loop counters often begin at 0 because JavaScript arrays and
strings use zero-based indexes.
for (
let index = 0;
index < 4;
index += 1
) {
console.log(index);
}
0
1
2
3
Why Use index < length?
The final valid array index is always one less than the array's
length. This makes index < array.length the
standard condition for forward array iteration.
const languages = [
"JavaScript",
"Python",
"SQL"
];
for (
let index = 0;
index < languages.length;
index += 1
) {
console.log(
languages[index]
);
}
JavaScript
Python
SQL
Common Off-by-One Error
const items = [
"A",
"B",
"C"
];
for (
let index = 0;
index <= items.length;
index += 1
) {
console.log(
items[index]
);
}
The condition uses <=, so the final iteration attempts to
access items[3]. The valid indexes are only
0, 1, and 2.
A
B
C
undefined
Counting Up
for (
let number = 2;
number <= 10;
number += 2
) {
console.log(number);
}
2
4
6
8
10
Counting Down
for (
let number = 5;
number >= 1;
number -= 1
) {
console.log(number);
}
console.log(
"Complete"
);
5
4
3
2
1
Complete
Custom Step Values
The update expression can increase or decrease the counter by any suitable amount.
for (
let number = 0;
number <= 20;
number += 5
) {
console.log(number);
}
0
5
10
15
20
Increment Operators
Several update expressions can produce the same numeric progression.
| Expression | Meaning |
|---|---|
index++ |
Increase index by one. |
++index |
Increase index by one. |
index += 1 |
Increase index by one. |
index-- |
Decrease index by one. |
index -= 1 |
Decrease index by one. |
index++ is common in loop headers, while
index += 1 makes the amount of change especially explicit.
Follow the style used consistently by the project.
Summing Numbers with a Loop
An accumulator variable stores a result that is updated during every iteration.
let total = 0;
for (
let number = 1;
number <= 5;
number += 1
) {
total += number;
}
console.log(total);
15
Calculating an Array Total
const prices = [
25,
40,
15,
20
];
let total = 0;
for (
let index = 0;
index < prices.length;
index += 1
) {
total += prices[index];
}
console.log(total);
100
Building a New Array
const numbers = [
1,
2,
3,
4
];
const doubledNumbers = [];
for (
let index = 0;
index < numbers.length;
index += 1
) {
doubledNumbers.push(
numbers[index] * 2
);
}
console.log(
doubledNumbers
);
[2, 4, 6, 8]
Filtering Values with a Loop
const numbers = [
1,
2,
3,
4,
5,
6
];
const evenNumbers = [];
for (
let index = 0;
index < numbers.length;
index += 1
) {
const number =
numbers[index];
if (
number % 2 === 0
) {
evenNumbers.push(
number
);
}
}
console.log(
evenNumbers
);
[2, 4, 6]
Accessing Both Index and Value
const languages = [
"JavaScript",
"Python",
"SQL"
];
for (
let index = 0;
index < languages.length;
index += 1
) {
console.log(
`${index}: ${languages[index]}`
);
}
0: JavaScript
1: Python
2: SQL
Changing Array Values by Index
A traditional for loop provides direct access to each array
index, making it possible to replace existing elements.
const numbers = [
1,
2,
3
];
for (
let index = 0;
index < numbers.length;
index += 1
) {
numbers[index] *= 2;
}
console.log(numbers);
[2, 4, 6]
Assigning to array[index] changes the original array. Build
a new array when the original data should remain unchanged.
Loop Variable Scope
A counter declared with let inside the loop header is
block-scoped and cannot be accessed after the loop.
for (
let index = 0;
index < 3;
index += 1
) {
console.log(index);
}
// ReferenceError
// console.log(index);
0
1
2
Using an Existing Counter Variable
Declare the counter outside the loop only when its final value is genuinely needed afterward.
let index = 0;
for (
;
index < 3;
index += 1
) {
console.log(index);
}
console.log(
`Final index: ${index}`
);
0
1
2
Final index: 3
Multiple Loop Variables
A for loop can initialize and update several variables using
commas.
for (
let left = 0,
right = 4;
left < right;
left += 1,
right -= 1
) {
console.log(
left,
right
);
}
0 4
1 3
Omitting for Loop Expressions
The initializer, condition, and update expressions are all optional, but the two semicolons must remain.
let index = 0;
for (
;
index < 3;
index += 1
) {
console.log(index);
}
0
1
2
Infinite for Loop
Omitting the condition creates a loop that continues until it is stopped by
break, return, throw, or an external
interruption.
let number = 1;
for (;;) {
console.log(number);
if (number === 3) {
break;
}
number += 1;
}
1
2
3
A loop that never reaches a falsy condition can freeze the browser tab, block the main thread, or consume excessive processor resources. Ensure that every intentionally finite loop has a reachable exit condition.
Accidental Infinite Loop
for (
let number = 1;
number <= 5;
number -= 1
) {
console.log(number);
}
The counter decreases while the condition expects it eventually to
exceed 5. The condition therefore never becomes false.
Another Infinite Loop Mistake
for (
let index = 0;
index < 5;
) {
console.log(index);
}
The loop has no update expression, and the body does not modify
index. Its condition remains true forever.
Empty Loop Body
An accidental semicolon after the loop header creates an empty loop body.
for (
let index = 0;
index < 3;
index += 1
);
{
console.log(
"Runs once after the loop"
);
}
The semicolon completes the loop statement. The following block is not part of the loop.
Creating a Multiplication Table
const multiplier = 5;
for (
let number = 1;
number <= 10;
number += 1
) {
const result =
multiplier * number;
console.log(
`${multiplier} × ${number} = ${result}`
);
}
5 × 1 = 5
5 × 2 = 10
5 × 3 = 15
5 × 4 = 20
5 × 5 = 25
5 × 6 = 30
5 × 7 = 35
5 × 8 = 40
5 × 9 = 45
5 × 10 = 50
Creating a String with a Loop
let output = "";
for (
let number = 1;
number <= 5;
number += 1
) {
output += `${number}`;
if (number < 5) {
output += ", ";
}
}
console.log(output);
1, 2, 3, 4, 5
Traditional for Loop Advantages
| Advantage | Description |
|---|---|
| Index control | The starting index, ending condition, and step are explicit. |
| Forward or reverse iteration | The counter can move in either direction. |
| Custom steps | The loop can skip elements or increment by any amount. |
| Array mutation | Elements can be replaced through their indexes. |
| Early loop control | break and continue can control execution. |
| Multiple counters | Several values can be initialized and updated together. |
When Another Loop May Be Clearer
| Goal | Alternative |
|---|---|
| Read every array value without needing its index | for...of |
| Repeat while an unpredictable condition remains true | while |
| Run the body at least once | do...while |
| Create a transformed array | map() |
| Keep matching array values | filter() |
| Calculate one result from an array | reduce() |
Common for Loop Mistakes
- Using
<= array.lengthwhen iterating over array indexes. - Updating the counter in the wrong direction.
- Forgetting to update the counter.
- Changing the wrong variable in the update expression.
- Adding an accidental semicolon after the loop header.
- Modifying an array while iterating without considering index changes.
- Using a counter declared with
varwhen block scope is expected. - Accessing the counter outside its
letscope. - Creating an infinite loop without a reliable exit condition.
- Repeating expensive calculations during every iteration unnecessarily.
- Mutating the original array when a new array should be created.
- Using a traditional loop when a clearer array method expresses the intention directly.
for Loop Quick Reference
| Goal | Pattern |
|---|---|
| Count upward | for (let i = 0; i < limit; i += 1) |
| Count downward | for (let i = limit; i >= 0; i -= 1) |
| Iterate over array indexes | for (let i = 0; i < array.length; i += 1) |
| Skip values | Use a larger update step such as i += 2. |
| Calculate a total | Declare an accumulator before the loop. |
| Build a new array | Push transformed or accepted values during each iteration. |
| Stop an intentional infinite loop | Use a reachable break, return, or throw. |
| Preserve the final counter | Declare the counter outside the loop. |
Use a traditional for loop when you need precise control over
indexes, direction, boundaries, or step size. Keep the condition easy to
verify, use block-scoped counters, and confirm that the update expression
always moves the loop toward completion.
Loops Block 1A Summary
- Loops repeat code while a condition remains true.
- A
forloop contains an initializer, condition, and update expression. - The initializer runs once before the first condition check.
- The condition is checked before every iteration.
- The update expression runs after every completed iteration.
- Array indexes normally begin at zero.
- The standard array boundary is
index < array.length. - Custom step values can skip or reverse iterations.
- Accumulator variables can build totals and other results.
- Traditional loops provide direct index access.
- Assigning to
array[index]mutates the original array. - Loop counters declared with
letare block-scoped. - Missing or incorrect updates can create infinite loops.
- An accidental semicolon can create an empty loop body.
- A different loop or array method may be clearer when index control is unnecessary.
JavaScript Loop Types
Choose a loop based on what controls the iteration. Use
while when repetition depends on a condition,
for...of for iterable values, and for...in
for enumerable object keys.
| Loop | Best used for | Important behavior |
|---|---|---|
while |
Repeating while a condition remains true | The condition is checked before each iteration. |
do...while |
Tasks that must run at least once | The condition is checked after the first iteration. |
for...of |
Arrays, strings, sets, maps, and other iterables | Returns values rather than array indexes. |
for...in |
Enumerating an object's enumerable keys | Can include inherited properties; use an ownership check when needed. |
The while and do...while Loops
A while loop may run zero times. A do...while
loop always runs once before its condition is evaluated. Make sure the
loop changes the state used by its condition to avoid an infinite loop.
let count = 1;
while (count <= 3) {
console.log(count);
count++;
}
let attempts = 0;
do {
attempts++;
} while (attempts < 1);
The for...of Loop
Use for...of when you need each value from an iterable.
It is usually clearer than a traditional index loop when the index is
not required. Use break to stop early or
continue to skip the current value.
const languages = ["JavaScript", "Python", "SQL"];
for (const language of languages) {
console.log(language);
}
The for...in Loop
Use for...in primarily for object keys. For arrays,
prefer for...of, array methods, or a traditional
for loop because for...in produces string keys
and may include additional enumerable properties.
const user = {
name: "Maya",
role: "Developer"
};
for (const key in user) {
if (Object.hasOwn(user, key)) {
console.log(key, user[key]);
}
}
for...of for values and
for...in for object keys. Use while when the
number of iterations is not known in advance.
JavaScript Nested Loops
A nested loop is a loop placed inside another loop. For every iteration of the outer loop, the inner loop completes its own iterations.
Nested loops are commonly used to process tables, grids, matrices, coordinates, combinations, grouped data, and relationships between two collections.
Basic Nested Loop
for (
let row = 1;
row <= 3;
row += 1
) {
for (
let column = 1;
column <= 2;
column += 1
) {
console.log(
`Row ${row}, Column ${column}`
);
}
}
Row 1, Column 1
Row 1, Column 2
Row 2, Column 1
Row 2, Column 2
Row 3, Column 1
Row 3, Column 2
Nested Loop Execution Order
| Step | Action |
|---|---|
| 1 | The outer loop begins its first iteration. |
| 2 | The inner loop runs from beginning to end. |
| 3 | The outer loop advances to its next iteration. |
| 4 | The inner loop starts again from its initial state. |
| 5 | The process repeats until the outer loop finishes. |
Total Number of Iterations
When the outer loop runs three times and the inner loop runs four times for each outer iteration, the inner body runs twelve times.
let iterations = 0;
for (
let outer = 0;
outer < 3;
outer += 1
) {
for (
let inner = 0;
inner < 4;
inner += 1
) {
iterations += 1;
}
}
console.log(iterations);
12
For two fixed-length loops, the approximate number of inner-body
executions is:
outer iterations × inner iterations.
Creating a Grid
const rows = 3;
const columns = 3;
for (
let row = 0;
row < rows;
row += 1
) {
for (
let column = 0;
column < columns;
column += 1
) {
console.log(
`(${row}, ${column})`
);
}
}
(0, 0)
(0, 1)
(0, 2)
(1, 0)
(1, 1)
(1, 2)
(2, 0)
(2, 1)
(2, 2)
Building Grid Rows
const rows = 3;
const columns = 5;
for (
let row = 0;
row < rows;
row += 1
) {
let line = "";
for (
let column = 0;
column < columns;
column += 1
) {
line += "#";
}
console.log(line);
}
#####
#####
#####
Creating a Triangle Pattern
const height = 5;
for (
let row = 1;
row <= height;
row += 1
) {
let line = "";
for (
let column = 1;
column <= row;
column += 1
) {
line += "*";
}
console.log(line);
}
*
**
***
****
*****
Processing a Matrix
A matrix is commonly represented as an array containing other arrays.
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
for (
const row of matrix
) {
for (
const value of row
) {
console.log(value);
}
}
1
2
3
4
5
6
7
8
9
Matrix Indexes and Values
const matrix = [
[10, 20],
[30, 40]
];
for (
const [
rowIndex,
row
] of matrix.entries()
) {
for (
const [
columnIndex,
value
] of row.entries()
) {
console.log(
`[${rowIndex}][${columnIndex}] = ${value}`
);
}
}
[0][0] = 10
[0][1] = 20
[1][0] = 30
[1][1] = 40
Summing a Matrix
const matrix = [
[1, 2, 3],
[4, 5, 6]
];
let total = 0;
for (
const row of matrix
) {
for (
const value of row
) {
total += value;
}
}
console.log(total);
21
Row Totals
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
const rowTotals = [];
for (
const row of matrix
) {
let rowTotal = 0;
for (
const value of row
) {
rowTotal += value;
}
rowTotals.push(
rowTotal
);
}
console.log(rowTotals);
[6, 15, 24]
Comparing Two Arrays
Nested loops can compare every value in one array with every value in another array.
const first = [
1,
2,
3
];
const second = [
2,
3,
4
];
const shared = [];
for (
const firstValue of first
) {
for (
const secondValue of second
) {
if (
firstValue ===
secondValue
) {
shared.push(
firstValue
);
break;
}
}
}
console.log(shared);
[2, 3]
For large arrays, converting one collection to a Set can
avoid repeatedly scanning it with an inner loop.
const secondSet =
new Set(second);
const shared =
first.filter(
value =>
secondSet.has(value)
);
Creating All Pair Combinations
const colors = [
"Red",
"Blue"
];
const sizes = [
"Small",
"Large"
];
const combinations = [];
for (
const color of colors
) {
for (
const size of sizes
) {
combinations.push(
`${color} - ${size}`
);
}
}
console.log(
combinations
);
[
"Red - Small",
"Red - Large",
"Blue - Small",
"Blue - Large"
]
Creating Unique Pairs
Start the inner loop after the current outer index to avoid comparing an item with itself and generating reversed duplicates.
const names = [
"Alice",
"Bob",
"Maya"
];
const pairs = [];
for (
let firstIndex = 0;
firstIndex < names.length;
firstIndex += 1
) {
for (
let secondIndex =
firstIndex + 1;
secondIndex <
names.length;
secondIndex += 1
) {
pairs.push(
[
names[firstIndex],
names[secondIndex]
]
);
}
}
console.log(pairs);
[
["Alice", "Bob"],
["Alice", "Maya"],
["Bob", "Maya"]
]
Finding Duplicate Values
const values = [
"A",
"B",
"C",
"B"
];
let duplicate = null;
for (
let firstIndex = 0;
firstIndex < values.length;
firstIndex += 1
) {
for (
let secondIndex =
firstIndex + 1;
secondIndex <
values.length;
secondIndex += 1
) {
if (
values[firstIndex] ===
values[secondIndex]
) {
duplicate =
values[firstIndex];
break;
}
}
if (duplicate !== null) {
break;
}
}
console.log(duplicate);
B
Breaking the Inner Loop
An ordinary break exits only the closest loop containing it.
The outer loop continues.
for (
let row = 1;
row <= 3;
row += 1
) {
console.log(
`Start row ${row}`
);
for (
let column = 1;
column <= 3;
column += 1
) {
if (column === 2) {
break;
}
console.log(
`Column ${column}`
);
}
console.log(
`End row ${row}`
);
}
Start row 1
Column 1
End row 1
Start row 2
Column 1
End row 2
Start row 3
Column 1
End row 3
Exiting Both Loops with a Flag
A Boolean flag can communicate to the outer loop that the search has finished.
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
const target = 5;
let found = false;
let position = null;
for (
let row = 0;
row < matrix.length;
row += 1
) {
for (
let column = 0;
column <
matrix[row].length;
column += 1
) {
if (
matrix[row][column] ===
target
) {
position = {
row,
column
};
found = true;
break;
}
}
if (found) {
break;
}
}
console.log(position);
{
row: 1,
column: 1
}
Exiting Nested Loops with return
When nested loops are inside a function, returning a result immediately is often clearer than maintaining an external flag.
function findPosition(
matrix,
target
) {
for (
let row = 0;
row < matrix.length;
row += 1
) {
for (
let column = 0;
column <
matrix[row].length;
column += 1
) {
if (
matrix[row][column] ===
target
) {
return {
row,
column
};
}
}
}
return null;
}
const matrix = [
[1, 2],
[3, 4]
];
console.log(
findPosition(
matrix,
4
)
);
{
row: 1,
column: 1
}
Use return when the complete function should finish after a
match. Use a flag when processing must continue after the nested loops.
Continuing the Inner Loop
An ordinary continue affects only the nearest loop.
const matrix = [
[1, 2, 3],
[4, 5, 6]
];
for (
const row of matrix
) {
for (
const value of row
) {
if (
value % 2 !== 0
) {
continue;
}
console.log(value);
}
}
2
4
6
Skipping an Entire Outer Iteration
A condition before the inner loop can skip the complete group represented by the outer iteration.
const matrix = [
[1, 2],
null,
[3, 4],
"invalid",
[5, 6]
];
for (
const row of matrix
) {
if (
!Array.isArray(row)
) {
continue;
}
for (
const value of row
) {
console.log(value);
}
}
1
2
3
4
5
6
Skipping Invalid Matrix Values
const matrix = [
[1, 2],
[null, 3],
"invalid",
[4, NaN]
];
let total = 0;
for (
const row of matrix
) {
if (
!Array.isArray(row)
) {
continue;
}
for (
const value of row
) {
if (
typeof value !==
"number" ||
!Number.isFinite(value)
) {
continue;
}
total += value;
}
}
console.log(total);
10
Nested Loop Variable Scope
Counters declared with let or const belong to
their own loop blocks.
for (
let row = 0;
row < 2;
row += 1
) {
for (
let column = 0;
column < 2;
column += 1
) {
console.log(
row,
column
);
}
// column is not
// available here
}
// row is not
// available here
Avoid Reusing the Same Variable Name
for (
let index = 0;
index < rows.length;
index += 1
) {
for (
let index = 0;
index <
rows[index].length;
index += 1
) {
// Difficult to read
}
}
Although block scoping can allow separate variables with the same name,
descriptive names such as rowIndex and
columnIndex make nested logic safer and clearer.
Uneven Matrix Rows
Each inner loop should use the length of its current row rather than assuming every row has the same number of values.
const rows = [
[1, 2, 3],
[4],
[5, 6]
];
for (
let rowIndex = 0;
rowIndex < rows.length;
rowIndex += 1
) {
for (
let columnIndex = 0;
columnIndex <
rows[rowIndex].length;
columnIndex += 1
) {
console.log(
rows[rowIndex][
columnIndex
]
);
}
}
1
2
3
4
5
6
Nested Loop Performance
Nested loops can perform a large amount of work as the collections grow.
Two loops that each process n values may perform approximately
n × n comparisons.
| Collection Size | Approximate Pair Comparisons |
|---|---|
| 10 values | 100 |
| 100 values | 10,000 |
| 1,000 values | 1,000,000 |
| 10,000 values | 100,000,000 |
Avoid Repeated Linear Searches
for (
const user of users
) {
for (
const allowedId of
allowedIds
) {
if (
user.id === allowedId
) {
// Process user
}
}
}
For large collections, place the allowed IDs in a Set and
perform one direct membership check per user.
const users = [
{
id: 1,
name: "Alice"
},
{
id: 2,
name: "Bob"
},
{
id: 3,
name: "Maya"
}
];
const allowedIds =
new Set([
1,
3
]);
for (
const user of users
) {
if (
!allowedIds.has(
user.id
)
) {
continue;
}
console.log(
user.name
);
}
Alice
Maya
Nested Loops vs flatMap()
When the goal is to create combinations or flatten transformed groups,
flatMap() may express the result more directly.
const colors = [
"Red",
"Blue"
];
const sizes = [
"Small",
"Large"
];
const combinations =
colors.flatMap(
color =>
sizes.map(
size =>
`${color} - ${size}`
)
);
console.log(
combinations
);
[
"Red - Small",
"Red - Large",
"Blue - Small",
"Blue - Large"
]
When Nested Loops Are Appropriate
| Situation | Reason |
|---|---|
| Processing rows and columns | Each row contains several column values. |
| Generating every combination | Each value must be paired with values from another collection. |
| Comparing unique pairs | Each item must be compared with later items. |
| Searching a matrix | The target may appear in any row and column. |
| Processing grouped collections | Each group contains its own iterable values. |
| Generating visual grids | Rows and columns determine each position. |
When to Replace Nested Loops
| Problem | Possible Alternative |
|---|---|
| Repeated membership checks | Use a Set. |
| Repeated key-based searches | Use a Map. |
| Creating nested transformed results | Consider map() or flatMap(). |
| Searching until the first match | Use a helper function with an early return. |
| Comparing sorted values | Consider a two-pointer technique. |
| Processing deeply nested data | Consider recursion or an explicit stack. |
Common Nested Loop Mistakes
- Expecting an ordinary
breakto exit every loop level. - Expecting an ordinary
continueto skip an outer iteration. - Reusing unclear counter names in inner and outer loops.
- Using the outer collection length for every inner row.
- Assuming all nested arrays have the same length.
- Failing to validate a row before iterating over it.
- Creating unnecessary duplicate or reversed pairs.
- Comparing every item with itself accidentally.
- Continuing expensive comparisons after a result has been found.
- Using nested loops for membership checks that a Set could handle.
- Creating very large combinations without considering memory usage.
- Mutating collections during nested iteration without reviewing the effects.
- Using deeply nested loops when the logic should be divided into functions.
- Ignoring how rapidly total iteration counts grow.
Nested Loop Quick Reference
| Goal | Pattern |
|---|---|
| Process rows and columns | Outer loop for rows, inner loop for columns. |
| Read a matrix | for (const row of matrix) for (const value of row) |
| Get matrix coordinates | Use nested entries() loops. |
| Create every pair across two arrays | Loop over the second array inside the first loop. |
| Create unique pairs in one array | Start the inner index at outerIndex + 1. |
| Exit only the inner loop | Use an ordinary break. |
| Exit both loops inside a function | Use an early return. |
| Exit both loops but continue afterward | Use a flag or a labeled statement. |
| Skip one inner value | Use continue inside the inner loop. |
| Skip an entire group | Use continue in the outer loop before entering the inner loop. |
| Improve repeated membership checks | Convert the lookup collection to a Set. |
Use nested loops when the data is genuinely two-dimensional or when every required combination must be processed. Give each counter a descriptive name, validate inner collections, stop searching when the result is known, and replace repeated inner searches with Sets or Maps when possible.
Loops Block 3A-3 Summary
- A nested loop places one loop inside another loop.
- The inner loop completes for every outer-loop iteration.
- Nested loops are useful for grids, matrices, combinations, and comparisons.
- Total work can grow by multiplying the iteration counts.
- Matrix rows can be processed with nested
for...ofloops. - Nested
entries()loops provide row and column indexes. - An ordinary
breakexits only the nearest loop. - An ordinary
continueaffects only the nearest loop. - A flag can communicate an inner-loop result to the outer loop.
- An early
returncan exit all nested loops inside a function. - Starting an inner index at
outerIndex + 1creates unique pairs. - Each inner loop should use its current row's length.
- Descriptive counter names prevent confusion and shadowing mistakes.
- Sets and Maps can replace many repeated inner searches.
- Nested-loop performance becomes increasingly important as collections grow.
JavaScript Loop Control Best Practices
JavaScript provides several statements for controlling loop execution. The correct statement depends on whether the current iteration, the complete loop, an outer loop, the current function, or normal program execution should end.
Choosing the correct control statement keeps loop behavior predictable, reduces unnecessary work, and prevents common errors such as accidental infinite loops, skipped state updates, and unclear nested-loop exits.
Loop Control Statement Overview
| Statement | Effect | Execution Continues |
|---|---|---|
break |
Ends the nearest loop or switch. | After the loop or switch. |
continue |
Skips the remainder of the current iteration. | At the next iteration. |
Labeled break |
Ends the targeted labeled statement. | After the labeled statement. |
Labeled continue |
Skips to the next iteration of the targeted loop. | At the targeted loop's next iteration. |
return |
Ends the current function. | At the function caller. |
throw |
Stops normal execution and raises an error. | At a matching error handler, when one exists. |
break vs continue
for (
let number = 1;
number <= 5;
number += 1
) {
if (number === 2) {
continue;
}
if (number === 4) {
break;
}
console.log(number);
}
console.log(
"Loop complete"
);
1
3
Loop complete
The value 2 is skipped by continue. When the
value reaches 4, break ends the complete loop,
so 5 is never processed.
break vs continue Decision Table
| Requirement | Use |
|---|---|
| Ignore one invalid value and keep processing | continue |
| Stop after finding the required result | break |
| Skip one disabled record | continue |
| Stop after reaching a sentinel value | break |
| Ignore one object property | continue |
| Stop after detecting a final validation failure | break or return |
| Process every accepted value | continue for rejected values. |
| Stop an intentional infinite loop | break |
break vs return
Use break when execution must continue after the loop. Use
return when the entire function should finish.
function findUserName(
users,
targetId
) {
let foundUser = null;
for (
const user of users
) {
if (
user.id === targetId
) {
foundUser = user;
break;
}
}
console.log(
"Search complete"
);
return (
foundUser?.name ??
null
);
}
function findUserName(
users,
targetId
) {
for (
const user of users
) {
if (
user.id === targetId
) {
return user.name;
}
}
return null;
}
break vs return Comparison
| Feature | break |
return |
|---|---|---|
| Ends the current loop | Yes | Yes, because the function ends. |
| Ends the current function | No | Yes |
| Can provide a result directly | No | Yes |
| Allows code after the loop to run | Yes | No, unless that code is outside the function. |
| Useful outside a function | Yes | No |
| Best for | Ending iteration while continuing the surrounding operation. | Ending the complete function after obtaining a result. |
break vs throw
A normal loop exit and an error represent different outcomes. Use
break for an expected stopping condition and
throw when invalid state prevents normal execution.
const values = [
10,
20,
"stop",
30
];
let total = 0;
for (
const value of values
) {
if (value === "stop") {
break;
}
total += value;
}
console.log(total);
30
function calculateTotal(
prices
) {
let total = 0;
for (
const price of prices
) {
if (
!Number.isFinite(
price
) ||
price < 0
) {
throw new TypeError(
"Invalid price."
);
}
total += price;
}
return total;
}
Normal Exit vs Error
| Situation | Recommended Statement |
|---|---|
| A requested item was found | break or return |
| A sentinel marks the end of relevant input | break |
| An optional item should be ignored | continue |
| A required function argument has the wrong type | throw |
| Data violates a strict function contract | throw |
| A search produced no match | Return a documented fallback such as null. |
Nested Loop Control Overview
| Goal | Technique |
|---|---|
| Exit the inner loop only | Ordinary break. |
| Skip one inner-loop value | Ordinary continue. |
| Exit an outer loop directly | Labeled break. |
| Skip the remainder of one outer iteration | Labeled continue. |
| Exit all loops and return a result | Place the loops in a function and use return. |
| Exit nested loops but continue afterward | Use a label or a Boolean flag. |
Labeled break Example
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
const target = 5;
let position = null;
matrixSearch:
for (
let row = 0;
row < matrix.length;
row += 1
) {
for (
let column = 0;
column <
matrix[row].length;
column += 1
) {
if (
matrix[row][column] ===
target
) {
position = {
row,
column
};
break matrixSearch;
}
}
}
console.log(position);
{
row: 1,
column: 1
}
Labeled continue Example
const groups = [
[10, 20],
[30, null],
[40, 50]
];
const acceptedGroups = [];
groupLoop:
for (
const group of groups
) {
for (
const value of group
) {
if (
!Number.isFinite(
value
)
) {
continue groupLoop;
}
}
acceptedGroups.push(
group
);
}
console.log(
acceptedGroups
);
[
[10, 20],
[40, 50]
]
Labels vs Helper Functions
| Technique | Advantages | Considerations |
|---|---|---|
| Label | Directly controls a specific nested loop. | May be unfamiliar and should be used sparingly. |
| Boolean flag | Uses familiar conditions. | May require repeated checks. |
| Helper function | Allows early return and isolates complex logic. | Creates another function boundary. |
| Array method | Can clearly describe simple searching or validation. | May not support advanced multi-level control. |
Safe continue Placement
Traditional for loops execute their update expression after
continue. Condition-controlled loops require more care because
their state updates normally occur inside the loop body.
let index = 0;
while (
index < values.length
) {
if (
values[index] === null
) {
continue;
}
index += 1;
}
When the current value is null, the counter never changes.
The loop repeats the same iteration forever.
const values = [
null,
10,
null,
20
];
let index = 0;
while (
index < values.length
) {
const value =
values[index];
index += 1;
if (value === null) {
continue;
}
console.log(value);
}
10
20
Loop State Checklist
| Question | Why It Matters |
|---|---|
| Which value controls loop termination? | That value must move toward a reachable stopping condition. |
| Can continue skip the state update? | This may create an infinite loop. |
| Can an error branch skip the update? | Every path must preserve loop progress. |
| Does collection mutation change indexes? | Values may be skipped or processed more than once. |
| Is there a safety limit? | Uncertain loops may need a maximum number of iterations. |
| Is the exit condition understandable? | Clear termination logic reduces maintenance errors. |
Boolean Flags
Flags can store loop results or control outer-loop behavior, but their names should clearly describe their meaning.
const numbers = [
3,
7,
12,
15
];
let hasEvenNumber =
false;
for (
const number of numbers
) {
if (
number % 2 === 0
) {
hasEvenNumber = true;
break;
}
}
console.log(
hasEvenNumber
);
true
let flag = false;
let status = true;
let done = false;
Prefer names such as hasMatch,
allValuesAreValid, shouldContinue, or
isProcessingComplete.
Loop Control and Array Methods
| Loop Goal | Loop Control | Array Alternative |
|---|---|---|
| Find the first match | Save the value and break. | find() |
| Find the first matching index | Save the index and break. | findIndex() |
| Check whether any value matches | Set a flag and break. | some() |
| Check whether all values match | Break on the first failure. | every() |
| Collect matching values | Continue past rejected values and push accepted ones. | filter() |
| Transform every value | Push transformed values into a new array. | map() |
| Calculate one accumulated result | Update an accumulator. | reduce() |
forEach() Control Limitations
The callback passed to forEach() does not support normal
break or continue statements.
numbers.forEach(
number => {
if (number === 3) {
// SyntaxError:
// break;
}
}
);
Use for...of, a traditional loop, or a suitable array search
method when early termination is required.
Returning from a forEach() Callback
A callback return ends only the current callback execution. It
does not end the complete forEach() operation or the outer
function.
const numbers = [
1,
2,
3,
4
];
numbers.forEach(
number => {
if (number === 2) {
return;
}
console.log(number);
}
);
1
3
4
Returning from a forEach() callback can skip the remaining
callback code for one element, but it does not provide true loop control
or early termination.
Loop Control Readability
| Practice | Benefit |
|---|---|
| Place simple guard conditions near the top | Rejected values leave the iteration early. |
| Keep break conditions visible | The loop's stopping behavior is easier to identify. |
| Use descriptive result variables | The loop's purpose is clearer. |
| Use descriptive labels | Nested-loop targets are easier to understand. |
| Extract complex conditions into named variables | Business rules become easier to read. |
| Extract large loop bodies into functions | Each function can express one responsibility. |
| Avoid unnecessary control statements | Normal loop completion may already be clear enough. |
Named Conditions
A descriptive Boolean expression can make loop-control decisions easier to understand.
const products = [
{
name: "Keyboard",
price: 100,
active: true
},
{
name: "Mouse",
price: -10,
active: true
}
];
for (
const product of products
) {
const hasValidName =
typeof product.name ===
"string" &&
product.name.trim() !== "";
const hasValidPrice =
Number.isFinite(
product.price
) &&
product.price >= 0;
if (!product.active) {
continue;
}
if (
!hasValidName ||
!hasValidPrice
) {
continue;
}
console.log(
product.name
);
}
Keyboard
Loop Control Best Practices
| Best Practice | Reason |
|---|---|
Use break only when the loop result is complete |
Prevents unnecessary early termination. |
Use continue for rejected individual values |
Keeps the main processing path flat. |
| Update while-loop state before continuing | Prevents infinite loops. |
| Store required results before breaking | Code after break does not execute. |
| Prefer return when the complete function should end | Avoids unnecessary flags and later checks. |
| Use throw only for genuine error conditions | Distinguishes expected flow from failures. |
| Use labels sparingly | Reduces unfamiliar and complex control flow. |
| Use exact validity checks | Preserves valid falsy values such as zero and false. |
| Stop searching when the final result is known | Reduces unnecessary processing. |
| Choose an array method when it expresses the intention clearly | May make common operations easier to understand. |
Common Loop Control Mistakes
- Using
continuewhen the complete loop should stop. - Using
breakwhen only one value should be skipped. - Expecting
breakto end the complete function. - Expecting an ordinary break to exit every nested loop.
- Expecting an ordinary continue to skip an outer-loop iteration.
- Skipping a while-loop state update with continue.
- Saving a search result after the break statement.
- Using throw for a normal no-match result.
- Silently ignoring critical invalid data with continue.
- Using ambiguous Boolean flags such as
flagorstatus. - Using several labels when a helper function would be clearer.
- Attempting to use break or continue inside
forEach(). - Expecting return from a forEach callback to stop the complete iteration.
- Using broad truthiness checks when zero or false are valid.
- Continuing a search after the final result is already known.
- Creating several unrelated exit paths in one complicated loop.
- Hiding loop termination inside an unnecessarily complex condition.
- Using control statements when normal loop completion is simpler.
Complete Loop Control Quick Reference
| Goal | Recommended Pattern |
|---|---|
| End the nearest loop | break; |
| Skip the current iteration | continue; |
| End a labeled outer loop | break outerLoop; |
| Continue a labeled outer loop | continue outerLoop; |
| End the complete function | return result; |
| Raise an error | throw new Error(message); |
| Find the first array value | find() or a loop with an early exit. |
| Find the first array index | findIndex(). |
| Check whether any value matches | some(). |
| Check whether all values match | every(). |
| Collect matching values | filter(). |
| Exit all nested loops with a result | Use a helper function with early return. |
| Skip invalid while-loop values safely | Update loop state before continue. |
| Stop uncertain repetition safely | Add a maximum iteration or attempt limit. |
Loop Control Review Checklist
| Question | Purpose |
|---|---|
| Should one iteration or the complete loop end? | Distinguishes continue from break. |
| Should the complete function end? | Determines whether return is more appropriate. |
| Is this an expected result or an error? | Distinguishes normal control flow from throw. |
| Which nested loop should be controlled? | Determines whether a label, flag, or function is needed. |
| Has the required result been saved? | Code after break or return will not execute. |
| Can every execution path update the loop state? | Prevents accidental infinite loops. |
| Can a built-in array method express the operation? | May simplify common search and validation patterns. |
| Is the control flow immediately understandable? | Improves maintenance and reduces logic errors. |
Use the smallest control statement that accurately represents the required behavior. Continue should reject one iteration, break should end one loop, return should end one function, and throw should report an exceptional failure. Keep nested-loop exits explicit, update all required state before continuing, and use array methods when they communicate the operation more directly.
Loops Block 3A-8 Summary
breakends the nearest loop or switch.continueskips the remainder of the current iteration.- Labeled break can end a specified outer loop.
- Labeled continue can skip to the next iteration of an outer loop.
returnends the complete function and may provide a result.throwreports an exceptional failure.- Break should be used for expected loop termination.
- Continue should be used for rejected individual values.
- While-loop state must be updated before a possible continue.
- Required results must be stored before break or return executes.
- An ordinary break or continue affects only the nearest loop.
- Labels should be descriptive and used sparingly.
- Helper functions with early return often simplify nested searches.
- Boolean flags should describe the state they represent.
- forEach does not support ordinary break or continue.
- Returning from a forEach callback does not stop the complete iteration.
- find, findIndex, some, every, and filter can replace common loop patterns.
- Exact validation checks preserve valid falsy values.
- Clear exit conditions prevent unnecessary work and infinite loops.
JavaScript Loop Best Practices
Good loop code should make its starting point, stopping condition, and update behavior easy to understand. Choose the loop type that most clearly expresses the task instead of using the same loop everywhere.
Choose the Right Loop
| Loop | Use It When | Avoid It When |
|---|---|---|
for |
The number of iterations or the numeric index is important. | You only need the values from an iterable collection. |
while |
Repetition depends on a condition and the number of iterations is unknown. | A simple fixed counter would be clearer. |
do...while |
The loop body must run at least once. | The condition should be checked before the first iteration. |
for...of |
You need the values from an iterable such as an array, string, Map, or Set. | You need enumerable property keys from a plain object. |
for...in |
You need enumerable property keys from an object. | You want array values in their normal sequence. |
Keep Loop Logic Predictable
const values = [
10,
20,
30
];
for (
let index = 0;
index < values.length;
index += 1
) {
const value =
values[index];
console.log(
index,
value
);
}
0 10
1 20
2 30
Loop Best Practices Checklist
-
Use descriptive names such as
index,row,column, oritem. - Make the starting value, condition, and update easy to see.
- Update a counter in one predictable place whenever possible.
-
Use
index < array.lengthfor normal forward index-based iteration. - Use separate counter variables for nested loops.
- Use braces even when the loop body contains only one statement.
- Keep loop bodies focused and move complex work into named functions.
-
Use
breakwhen processing should stop after a known condition. -
Use
continuesparingly and ensure it cannot skip required updates. - Avoid structurally changing a collection while iterating through it unless the behavior is intentional and carefully controlled.
-
Use
Object.hasOwn()when afor...inloop should process only an object's own properties. - Add a maximum iteration limit when termination depends on uncertain calculated or external state.
Prefer Readable Loop Bodies
for (
let index = 0;
index < records.length;
index += 1
) {
// Validation
// Transformation
// Permission checks
// DOM updates
// Logging
// Error handling
}
When a loop performs several unrelated tasks, extract named functions or split the process into clear stages. A shorter loop body is usually easier to test and maintain.
Extract Complex Work
function processRecord(
record
) {
if (!record.active) {
return;
}
console.log(
`Processing ${record.name}`
);
}
for (
const record of records
) {
processRecord(record);
}
Correctness Before Micro-Optimization
Choose a loop because it expresses the required behavior clearly. In most everyday code, readability and correct termination are more important than small theoretical performance differences between loop types.
Optimize only after measuring a real performance problem. First ensure that the loop uses the correct boundaries, avoids unnecessary repeated work, and terminates safely.
Block 6 Summary
- Choose the loop that most clearly matches the task.
- Use
forwhen indexes or fixed iteration counts matter. - Use
whilewhen repetition depends on a condition. - Use
do...whilewhen the body must run at least once. - Use
for...offor iterable values. - Use
for...infor enumerable object keys. - Keep conditions, updates, and exit paths easy to verify.
- Use braces and descriptive loop-variable names.
- Extract complex loop-body logic into named functions.
- Prioritize correctness and readability before optimization.
JavaScript Loops Quick Reference
Use this quick reference to choose the right loop for your task. It summarizes the syntax, purpose, advantages, and common use cases of every JavaScript loop covered in this chapter.
| Loop | Best Used For | Example |
|---|---|---|
for |
Known number of iterations or index-based loops. |
for (let i = 0; i < 5; i++)
|
while |
Loop until a condition becomes false. |
while (running)
|
do...while |
Execute the body at least once. |
do { ... } while (...)
|
for...of |
Iterate over iterable values. |
for (const item of array)
|
for...in |
Iterate over enumerable object keys. |
for (const key in object)
|
Loop Selection Guide
| If You Want To... | Use |
|---|---|
| Count from 1 to 100 | for |
| Repeat until something happens | while |
| Run at least once | do...while |
| Read every array value | for...of |
| Loop through object properties | for...in |
| Stop a loop early | break |
| Skip the current iteration | continue |
- Use
forwhen you need an index. - Use
whilewhen you don't know how many iterations are needed. - Use
do...whilewhen the loop must execute at least once. - Use
for...offor iterable values. - Use
for...infor object keys. - Use
breakto exit a loop immediately. - Use
continueto skip only the current iteration. - Always verify the start value, stop condition, and counter update.
Chapter Complete
You have learned how to use every major JavaScript loop, control
loop execution with break and continue,
avoid common mistakes, and choose the right loop for different
programming tasks.
Introduction to the DOM
The Document Object Model (DOM) is a programming interface that represents an HTML document as a tree of objects. JavaScript uses the DOM to find, read, modify, create, and remove HTML elements.
How the DOM Works
When a web page loads, the browser converts the HTML document into a structured tree. Every HTML element becomes a JavaScript object that can be accessed and modified.
<body>
<h1>Hello</h1>
<p>Welcome!</p>
</body>
document
└── body
├── h1
└── p
Accessing the DOM
JavaScript starts from the global document object. From
there you can select and manipulate any element on the page.
const heading =
document.querySelector("h1");
console.log(heading);
<h1>Hello</h1>
What Can You Do with the DOM?
| Task | Example |
|---|---|
| Select elements | querySelector() |
| Read content | textContent |
| Change content | innerHTML |
| Modify classes | classList |
| Create elements | createElement() |
| Remove elements | remove() |
HTML defines the page structure, CSS controls the appearance, and JavaScript uses the DOM to interact with both.
Summary
- The DOM represents an HTML document as a tree of objects.
- JavaScript accesses the page through the
documentobject. - Every HTML element becomes a DOM node.
- You can read, update, create, and remove elements.
- The DOM connects HTML, CSS, and JavaScript.
Selecting DOM Elements
Before JavaScript can modify an element, it must first select it.
The most commonly used methods are
getElementById(),
querySelector(), and
querySelectorAll().
Common Selection Methods
| Method | Selects | Returns |
|---|---|---|
getElementById() |
An element by its ID | One element |
querySelector() |
First matching CSS selector | One element |
querySelectorAll() |
All matching CSS selectors | NodeList |
Select an Element by ID
const title =
document.getElementById("title");
console.log(title);
Select the First Matching Element
const button =
document.querySelector(".btn");
console.log(button);
Select Multiple Elements
const items =
document.querySelectorAll(".item");
console.log(items);
NodeList(3)
- Use
getElementById()when selecting a unique ID. - Use
querySelector()for a single CSS selector match. - Use
querySelectorAll()when multiple elements are needed.
Summary
getElementById()selects one element by ID.querySelector()returns the first matching element.querySelectorAll()returns all matching elements as a NodeList.querySelector()andquerySelectorAll()use CSS selectors.- Select elements before reading or modifying them.
Reading & Updating Content
JavaScript can read and update the content of HTML elements.
The three most commonly used properties are
textContent,
innerText, and
innerHTML.
Content Properties
| Property | Description | Best Use |
|---|---|---|
textContent |
Reads or changes plain text. | Recommended for most cases. |
innerText |
Reads only visible text. | When CSS visibility matters. |
innerHTML |
Reads or writes HTML markup. | Insert HTML elements. |
Update Plain Text
const title =
document.querySelector("h1");
title.textContent =
"JavaScript Cheat Sheet";
Insert HTML
const box =
document.querySelector(".box");
box.innerHTML =
"<strong>Hello!</strong>";
Read Visible Text
const heading =
document.querySelector("h1");
console.log(
heading.innerText
);
Avoid inserting untrusted user input with
innerHTML. It can introduce
security risks such as Cross-Site Scripting (XSS).
Use textContent whenever you only need
to display text. Use innerHTML only when
you intentionally need to insert HTML.
Summary
textContentis the safest choice for text.innerTextreturns visible text.innerHTMLreads and writes HTML markup.- Prefer
textContentunless HTML is required. - Never insert untrusted HTML with
innerHTML.
Working with HTML Attributes
HTML attributes provide additional information about elements. JavaScript can read, create, update, and remove attributes using the DOM API.
Common Attribute Methods
| Method | Description |
|---|---|
getAttribute() |
Returns an attribute value. |
setAttribute() |
Creates or updates an attribute. |
removeAttribute() |
Removes an attribute. |
hasAttribute() |
Checks whether an attribute exists. |
Read an Attribute
const image =
document.querySelector("img");
console.log(
image.getAttribute("src")
);
Set an Attribute
const link =
document.querySelector("a");
link.setAttribute(
"target",
"_blank"
);
Remove an Attribute
const input =
document.querySelector("input");
input.removeAttribute(
"disabled"
);
Check if an Attribute Exists
const image =
document.querySelector("img");
console.log(
image.hasAttribute("alt")
);
Use DOM properties such as
element.value,
element.href, and
element.checked
when available. Use attribute methods for generic HTML attributes.
Summary
getAttribute()reads an attribute.setAttribute()creates or updates an attribute.removeAttribute()removes an attribute.hasAttribute()checks if an attribute exists.- Use properties when working with built-in element values.
Managing CSS Classes
The classList property provides an easy way to add, remove,
toggle, replace, and check CSS classes on HTML elements.
Common classList Methods
| Method | Description |
|---|---|
add() |
Add one or more classes. |
remove() |
Remove one or more classes. |
toggle() |
Add or remove a class automatically. |
contains() |
Check if a class exists. |
replace() |
Replace one class with another. |
Add and Remove Classes
const button =
document.querySelector(".btn");
button.classList.add("active");
button.classList.remove("disabled");
Toggle a Class
const menu =
document.querySelector(".menu");
menu.classList.toggle("open");
Check or Replace a Class
const card =
document.querySelector(".card");
if (
card.classList.contains("dark")
) {
card.classList.replace(
"dark",
"light"
);
}
Prefer changing CSS classes instead of modifying inline styles. It keeps JavaScript focused on behavior and CSS responsible for presentation.
Summary
add()adds CSS classes.remove()removes CSS classes.toggle()switches a class on or off.contains()checks whether a class exists.replace()swaps one class for another.- Prefer
classListover changing inline styles.
Working with Inline Styles
The style property lets JavaScript read and modify an
element's inline CSS. It is useful for simple style changes, while
classList is usually the better choice for larger UI updates.
Common Style Properties
| Property | Example |
|---|---|
| Color | element.style.color = "red" |
| Background | element.style.backgroundColor = "gold" |
| Font Size | element.style.fontSize = "20px" |
| Display | element.style.display = "none" |
| Width | element.style.width = "300px" |
Change an Inline Style
const heading =
document.querySelector("h1");
heading.style.color =
"royalblue";
heading.style.fontSize =
"36px";
Show or Hide an Element
const menu =
document.querySelector(".menu");
menu.style.display =
"none";
Read an Inline Style
const box =
document.querySelector(".box");
console.log(
box.style.width
);
element.style only accesses inline styles. Styles applied
through external CSS files are not returned unless you use
getComputedStyle().
Use style for small, temporary changes. For themes, states,
and larger visual updates, prefer classList and let CSS
handle the styling.
Summary
element.stylemodifies inline CSS.- Property names use camelCase (for example
backgroundColor). - Use CSS units such as
pxwhen required. display = "none"hides an element.- Prefer
classListfor larger style changes.
Creating Elements
JavaScript can create new HTML elements and insert them into the page.
The most commonly used methods are
createElement(),
append(),
appendChild(), and
prepend().
Common Methods
| Method | Description |
|---|---|
createElement() |
Create a new HTML element. |
append() |
Insert content at the end of an element. |
appendChild() |
Append a DOM node as the last child. |
prepend() |
Insert content as the first child. |
Create and Append an Element
const paragraph =
document.createElement("p");
paragraph.textContent =
"Hello, DOM!";
document.body.append(
paragraph
);
Append an Existing Element
const list =
document.querySelector("ul");
const item =
document.createElement("li");
item.textContent =
"JavaScript";
list.appendChild(item);
Insert at the Beginning
const list =
document.querySelector("ul");
const item =
document.createElement("li");
item.textContent =
"First Item";
list.prepend(item);
Create elements first, set their text or attributes, and then insert them into the DOM. This keeps your code easy to read and maintain.
Summary
createElement()creates a new element.append()inserts content at the end.appendChild()appends a DOM node.prepend()inserts content at the beginning.- Create, configure, then insert new elements.
Removing & Replacing Elements
JavaScript can remove existing elements or replace them with new ones. These operations are commonly used when updating the page dynamically.
Common Methods
| Method | Description |
|---|---|
remove() |
Removes an element from the DOM. |
replaceWith() |
Replaces an element with another element. |
Remove an Element
const message =
document.querySelector(".message");
message.remove();
Replace an Element
const oldHeading =
document.querySelector("h1");
const newHeading =
document.createElement("h2");
newHeading.textContent =
"JavaScript DOM";
oldHeading.replaceWith(
newHeading
);
Before removing an element, make sure it exists. Before replacing an element, fully configure the new element so it is ready to insert into the DOM.
Calling remove() permanently removes the element from the
document. If you need it later, create it again or keep a reference
before removing it.
Summary
remove()deletes an element from the page.replaceWith()swaps one element for another.- Create and configure replacement elements before inserting them.
- Check that an element exists before removing it.
- Removing an element also removes it from the document tree.
Navigating the DOM Tree
DOM traversing lets you move between related elements such as parents, children, and siblings. These properties are useful when working with existing page structures.
Common Traversing Properties
| Property | Description |
|---|---|
parentElement |
Returns the parent element. |
children |
Returns all child elements. |
firstElementChild |
Returns the first child element. |
lastElementChild |
Returns the last child element. |
nextElementSibling |
Returns the next sibling element. |
previousElementSibling |
Returns the previous sibling element. |
Navigate Between Elements
const item =
document.querySelector(".item");
console.log(
item.parentElement
);
console.log(
item.nextElementSibling
);
console.log(
item.previousElementSibling
);
Access Child Elements
const list =
document.querySelector("ul");
console.log(
list.children
);
console.log(
list.firstElementChild
);
console.log(
list.lastElementChild
);
Traverse the DOM only when elements have a stable relationship. When possible, select elements directly using IDs or CSS selectors to make your code easier to read and maintain.
Summary
parentElementmoves up the DOM tree.childrenreturns all child elements.firstElementChildandlastElementChildaccess the first and last child.nextElementSiblingandpreviousElementSiblingmove between siblings.- Prefer direct selectors when possible.
DOM Quick Reference
Use this quick reference to find the most common DOM methods and properties for selecting, modifying, creating, and navigating HTML elements.
| Task | Method / Property |
|---|---|
| Select by ID | getElementById() |
| Select first match | querySelector() |
| Select all matches | querySelectorAll() |
| Read or change text | textContent |
| Read or insert HTML | innerHTML |
| Read visible text | innerText |
| Add a class | classList.add() |
| Remove a class | classList.remove() |
| Toggle a class | classList.toggle() |
| Modify inline CSS | element.style |
| Create an element | createElement() |
| Append as last child | append() / appendChild() |
| Insert as first child | prepend() |
| Remove an element | remove() |
| Replace an element | replaceWith() |
| Parent element | parentElement |
| Child elements | children |
| First child | firstElementChild |
| Last child | lastElementChild |
| Next sibling | nextElementSibling |
| Previous sibling | previousElementSibling |
In modern JavaScript, querySelector(),
querySelectorAll(), and classList cover most
everyday DOM manipulation tasks.
DOM Chapter Complete
- Select elements using IDs or CSS selectors.
- Read and update text or HTML content.
- Manage attributes, classes, and inline styles.
- Create, insert, remove, and replace elements.
- Navigate between parent, child, and sibling elements.
Introduction to JavaScript Events
Events are actions that occur in the browser, such as clicking a button, typing into a form, moving the mouse, or pressing a key. JavaScript can listen for these events and execute code in response.
Common Browser Events
| Event | Occurs When |
|---|---|
click |
The user clicks an element. |
dblclick |
The user double-clicks an element. |
input |
The value of an input changes. |
change |
An input value is committed. |
keydown |
A keyboard key is pressed. |
submit |
A form is submitted. |
mouseover |
The pointer enters an element. |
Simple Event Example
const button =
document.querySelector("button");
button.addEventListener(
"click",
() => {
console.log("Button clicked!");
}
);
Button clicked!
Use addEventListener() instead of inline HTML event
attributes such as onclick. It keeps JavaScript separate
from your HTML and makes code easier to maintain.
Summary
- Events represent user or browser actions.
- JavaScript responds to events by running functions.
clickis one of the most commonly used events.addEventListener()is the preferred way to register events.- Separate JavaScript behavior from HTML markup.
Using addEventListener()
The addEventListener() method registers a function that runs
whenever a specific event occurs. It is the standard way to handle events
in modern JavaScript.
Syntax
element.addEventListener(
"event",
callback
);
Click Event
const button =
document.querySelector("button");
button.addEventListener(
"click",
() => {
console.log("Clicked!");
}
);
Clicked!
Using a Named Function
const button =
document.querySelector("button");
function showMessage() {
console.log("Hello!");
}
button.addEventListener(
"click",
showMessage
);
Removing an Event Listener
button.removeEventListener(
"click",
showMessage
);
Anonymous arrow functions cannot be removed with
removeEventListener(). Use a named function when you plan to
remove the listener later.
Keep event callbacks short. If the logic becomes large, call a separate function instead of placing all code directly inside the event listener.
Summary
addEventListener()registers an event handler.- The callback runs when the event occurs.
- Arrow functions work well for simple handlers.
- Use named functions if the listener needs to be removed.
removeEventListener()unregisters an event listener.
Common JavaScript Events
JavaScript supports many browser events. The events below are the ones you'll use most often when building interactive web pages.
Frequently Used Events
| Event | Triggered When | Typical Use |
|---|---|---|
click |
User clicks an element. | Buttons, menus |
dblclick |
User double-clicks. | Editors, image viewers |
input |
Input value changes. | Live search, validation |
change |
Input value is committed. | Forms, dropdowns |
submit |
Form is submitted. | Form processing |
keydown |
A key is pressed. | Keyboard shortcuts |
keyup |
A key is released. | Search, typing detection |
mouseenter |
Pointer enters an element. | Hover effects |
mouseleave |
Pointer leaves an element. | Hide tooltips, menus |
Example: Listening for Different Events
const input =
document.querySelector("input");
input.addEventListener(
"input",
() => {
console.log("Typing...");
}
);
input.addEventListener(
"change",
() => {
console.log("Value changed");
}
);
Typing...
Value changed
Use input when you need updates while the user types.
Use change when you only need the final value after editing
is complete.
Summary
clickis the most commonly used event.inputfires continuously while typing.changefires after editing is complete.submithandles form submissions.keydownandkeyuprespond to keyboard input.mouseenterandmouseleaveare useful for hover interactions.
The Event Object
When an event occurs, JavaScript passes an event object to the callback function. This object contains useful information about the event, such as the element that triggered it, the event type, and keyboard or mouse details.
Common Event Properties
| Property | Description |
|---|---|
event.target |
The element that triggered the event. |
event.type |
The event name (for example click). |
event.key |
The key pressed during keyboard events. |
event.preventDefault() |
Prevents the browser's default behavior. |
Access the Event Object
const button =
document.querySelector("button");
button.addEventListener(
"click",
(event) => {
console.log(event.type);
console.log(event.target);
}
);
click
<button>...</button>
Prevent Default Behavior
const form =
document.querySelector("form");
form.addEventListener(
"submit",
(event) => {
event.preventDefault();
console.log(
"Form prevented"
);
}
);
Name the parameter event or the shorter
e. Use preventDefault() only when you
intentionally want to override the browser's default action.
Summary
- The event object is passed to every event callback.
event.targetidentifies the element that fired the event.event.typereturns the event name.event.keyis useful for keyboard events.preventDefault()stops the browser's default behavior.
Event Bubbling & Capturing
When an event occurs, it travels through the DOM. By default, most events bubble upward from the target element to its parent elements.
Propagation Phases
| Phase | Description |
|---|---|
| Capturing | Event travels from the document down to the target element. |
| Target | The event reaches the element that triggered it. |
| Bubbling | Event bubbles back up through the parent elements. |
Event Bubbling Example
const parent =
document.querySelector(".parent");
const child =
document.querySelector(".child");
parent.addEventListener("click", () => {
console.log("Parent");
});
child.addEventListener("click", () => {
console.log("Child");
});
Child
Parent
Stop Bubbling
child.addEventListener(
"click",
(event) => {
event.stopPropagation();
console.log("Child");
}
);
Let events bubble unless you have a specific reason to stop them. Event bubbling makes techniques like event delegation possible.
Summary
- Most events bubble up the DOM tree.
- The event reaches the target before bubbling upward.
stopPropagation()stops the event from continuing.- Capturing occurs before the target but is used less often.
- Event bubbling is commonly used with event delegation.
Event Delegation
Event delegation attaches a single event listener to a parent element instead of adding listeners to every child. Thanks to event bubbling, the parent can handle events from its children.
Why Use Event Delegation?
| Benefit | Description |
|---|---|
| Better Performance | One listener instead of many. |
| Cleaner Code | Less repetitive event handling. |
| Dynamic Elements | Works for elements added later. |
Example
const list =
document.querySelector("ul");
list.addEventListener(
"click",
(event) => {
if (
event.target.matches("li")
) {
console.log(
event.target.textContent
);
}
}
);
Clicked list item
Event delegation is ideal for lists, tables, menus, and any interface where elements may be added or removed dynamically.
Summary
- Attach one listener to a parent element.
- Use
event.targetto identify the clicked child. matches()filters the elements you want to handle.- Works with dynamically created elements.
- Improves performance and reduces duplicate code.
Events Quick Reference
Use this reference to quickly find the most common JavaScript events, methods, and event object properties.
| Task | Method / Event |
|---|---|
| Register an event | addEventListener() |
| Remove an event | removeEventListener() |
| Mouse click | click |
| Double click | dblclick |
| Typing | input |
| Value changed | change |
| Form submission | submit |
| Key pressed | keydown |
| Key released | keyup |
| Mouse enters | mouseenter |
| Mouse leaves | mouseleave |
| Triggered element | event.target |
| Event type | event.type |
| Pressed key | event.key |
| Prevent default action | event.preventDefault() |
| Stop bubbling | event.stopPropagation() |
| Match delegated element | event.target.matches() |
Modern JavaScript event handling is built around
addEventListener(), the event object, and event delegation.
Master these concepts and you'll cover most real-world use cases.
Events Chapter Complete
- Register events with
addEventListener(). - Access event information through the event object.
- Handle mouse, keyboard, form, and input events.
- Use bubbling and delegation for efficient event handling.
- Prevent default behavior or stop propagation when needed.
Introduction to Async JavaScript
JavaScript normally executes code one statement at a time. Asynchronous JavaScript allows long-running tasks, such as network requests and timers, to run without blocking the rest of the application.
Synchronous vs Asynchronous
| Type | Behavior |
|---|---|
| Synchronous | Runs one statement after another. |
| Asynchronous | Allows other code to continue while waiting. |
Simple Example
console.log("Start");
setTimeout(() => {
console.log("Finished");
}, 2000);
console.log("End");
Start
End
Finished
Asynchronous code doesn't stop the rest of your JavaScript from running. Instead, it schedules work to happen later.
Summary
- Synchronous code runs step by step.
- Asynchronous code lets other work continue.
setTimeout()is a simple async example.- Async programming keeps applications responsive.
- Promises and
async/awaitbuild on these concepts.
JavaScript Callbacks
A callback is a function passed to another function and executed later. Callbacks are commonly used with timers, events, and older asynchronous APIs.
Callback Syntax
function processUser(name, callback) {
console.log(`Hello, ${name}`);
callback();
}
function finish() {
console.log("Finished");
}
processUser("Alice", finish);
Hello, Alice
Finished
Asynchronous Callback
function loadData(callback) {
setTimeout(() => {
const data = {
id: 1,
name: "Alice"
};
callback(data);
}, 1000);
}
loadData((data) => {
console.log(data);
});
{
id: 1,
name: "Alice"
}
Callback Types
| Type | Behavior |
|---|---|
| Synchronous callback | Runs immediately during the current operation. |
| Asynchronous callback | Runs later after a timer, event, or async operation completes. |
| Named callback | Uses a reusable function reference. |
| Anonymous callback | Defines the function directly where it is passed. |
firstTask(() => {
secondTask(() => {
thirdTask(() => {
console.log("Done");
});
});
});
Deep nesting is often called callback hell. Promises and
async/await usually produce flatter,
easier-to-read asynchronous code.
Use descriptive named callbacks when the function is reused or contains more than a few lines. Use short inline callbacks only for simple logic.
Summary
- A callback is a function passed to another function.
- The receiving function decides when to execute it.
- Callbacks can be synchronous or asynchronous.
- Timers and events commonly use callbacks.
- Avoid deeply nested callback structures.
- Promises provide a cleaner alternative for many async tasks.
JavaScript Promises
A Promise represents the future result of an asynchronous operation.
Use then() for successful results,
catch() for errors, and finally() for cleanup.
Promise States
| State | Description |
|---|---|
pending |
The operation is still running. |
fulfilled |
The operation completed successfully. |
rejected |
The operation failed. |
Create a Promise
const request = new Promise(
(resolve, reject) => {
const success = true;
if (success) {
resolve("Data loaded");
} else {
reject(
new Error("Request failed")
);
}
}
);
Handle a Promise
request
.then((result) => {
console.log(result);
})
.catch((error) => {
console.error(
error.message
);
})
.finally(() => {
console.log("Complete");
});
Data loaded
Complete
Promise Chaining
Promise.resolve(5)
.then((number) => {
return number * 2;
})
.then((number) => {
console.log(number);
})
.catch((error) => {
console.error(error);
});
10
Return the next value or Promise from each then() callback.
Without return, the next step receives
undefined.
Always handle rejected Promises with catch(). Use
async/await when it makes longer Promise chains
easier to read.
Summary
- A Promise represents a future asynchronous result.
resolve()fulfills a Promise.reject()rejects a Promise.then()handles successful results.catch()handles errors.finally()runs after completion.
JavaScript async and await
The async and await keywords provide a cleaner
way to work with Promises. An async function always returns a Promise,
while await pauses that function until a Promise settles.
Basic Syntax
async function getMessage() {
return "Hello!";
}
getMessage().then((message) => {
console.log(message);
});
Hello!
Wait for a Promise
function loadMessage() {
return Promise.resolve(
"Data loaded"
);
}
async function showMessage() {
const message =
await loadMessage();
console.log(message);
}
showMessage();
Data loaded
Promise Chain vs async/await
| Promises | async / await |
|---|---|
then() handles results. |
await stores the resolved result. |
catch() handles errors. |
try...catch handles errors. |
| Useful for short chains. | Often clearer for several sequential steps. |
Sequential Async Steps
async function loadProfile() {
const user =
await getUser();
const posts =
await getPosts(user.id);
console.log(user, posts);
}
loadProfile();
Use await inside an async function. Top-level
await is also available in JavaScript modules.
Use await when one asynchronous step depends on the result
of the previous step. Independent tasks can often run together instead
of waiting one after another.
Summary
- An
asyncfunction always returns a Promise. awaitwaits for a Promise inside an async function.- The resolved value can be assigned to a variable.
- Async/await often makes sequential code easier to read.
- Use
try...catchto handle rejected Promises. - Avoid sequential waits when tasks can run independently.
JavaScript Promise Utilities
Promise utility methods coordinate multiple asynchronous operations. Choose the method based on whether you need every result, the first settled result, or the first successful result.
Common Promise Methods
| Method | Resolves When | Result |
|---|---|---|
Promise.all() |
All Promises fulfill. | Array of values. |
Promise.allSettled() |
All Promises settle. | Array of status objects. |
Promise.race() |
The first Promise settles. | First fulfilled value or rejection. |
Promise.any() |
The first Promise fulfills. | First successful value. |
Wait for All Promises
async function loadDashboard() {
const [user, posts] =
await Promise.all([
getUser(),
getPosts()
]);
console.log(user, posts);
}
loadDashboard();
Keep Every Result
const results =
await Promise.allSettled([
Promise.resolve("Loaded"),
Promise.reject(
new Error("Failed")
)
]);
console.log(results);
[
{
status: "fulfilled",
value: "Loaded"
},
{
status: "rejected",
reason: Error("Failed")
}
]
Use the First Result
const firstSettled =
await Promise.race([
requestA(),
requestB()
]);
const firstSuccessful =
await Promise.any([
requestA(),
requestB()
]);
If one Promise rejects, Promise.all() rejects immediately.
Use Promise.allSettled() when every outcome must be collected.
Use Promise.all() for independent tasks that must all
succeed. Running them together is usually faster than awaiting each one
sequentially.
Summary
Promise.all()requires every Promise to fulfill.Promise.allSettled()preserves every outcome.Promise.race()uses the first settled Promise.Promise.any()uses the first fulfilled Promise.- Run independent asynchronous tasks together when possible.
Async JavaScript Quick Reference
Use this reference to quickly find the most common asynchronous JavaScript methods, keywords, and Promise utilities.
| Task | Method / Keyword |
|---|---|
| Create a delay | setTimeout() |
| Pass a callback | callback() |
| Create a Promise | new Promise() |
| Resolve a Promise | resolve() |
| Reject a Promise | reject() |
| Handle success | .then() |
| Handle errors | .catch() |
| Always execute | .finally() |
| Create an async function | async function |
| Wait for a Promise | await |
| Handle async errors | try...catch |
| Wait for all Promises | Promise.all() |
| Wait for all results | Promise.allSettled() |
| Use first settled Promise | Promise.race() |
| Use first fulfilled Promise | Promise.any() |
Modern JavaScript primarily uses
async,
await, and
Promise.all().
Understanding these covers most real-world asynchronous programming.
Async JavaScript Chapter Complete
- Callbacks introduced asynchronous programming.
- Promises provide structured async workflows.
asyncandawaitsimplify Promise handling.- Use
try...catchfor async error handling. - Promise utilities coordinate multiple asynchronous tasks efficiently.
Introduction to JSON
JSON (JavaScript Object Notation) is a lightweight text format used to store and exchange data. It is widely used by APIs, web applications, and servers because it is easy for both humans and machines to read.
JSON Data Types
| Type | Example |
|---|---|
| String | "Alice" |
| Number | 25 |
| Boolean | true |
| Array | ["HTML","CSS"] |
| Object | {"name":"Alice"} |
| null | null |
JSON Example
{
"name": "Alice",
"age": 25,
"active": true,
"skills": [
"JavaScript",
"CSS"
]
}
JavaScript Object vs JSON
| JavaScript Object | JSON |
|---|---|
| Property names may be unquoted. | Property names must use double quotes. |
| Can contain functions. | Functions are not allowed. |
| Regular JavaScript object. | Plain text data format. |
JSON is text, not a JavaScript object. It must follow strict syntax, including double quotes around property names and string values.
Use JSON when sending or storing data. Convert it to JavaScript objects before working with it in your code.
Summary
- JSON is a text-based data format.
- It is commonly used by APIs.
- JSON supports objects, arrays, strings, numbers, booleans and null.
- Functions and comments are not valid JSON.
- JSON must use double quotes.
JSON.parse() and JSON.stringify()
Use JSON.parse() to convert JSON text into a JavaScript
value. Use JSON.stringify() to convert a JavaScript value
into JSON text.
Quick Comparison
| Method | Converts | Returns |
|---|---|---|
JSON.parse() |
JSON text → JavaScript | Object, array, string, number, boolean, or null |
JSON.stringify() |
JavaScript → JSON text | String |
Convert JSON Text to an Object
const json = `{
"name": "Alice",
"age": 25
}`;
const user =
JSON.parse(json);
console.log(user.name);
Alice
Convert an Object to JSON Text
const user = {
name: "Alice",
age: 25
};
const json =
JSON.stringify(user);
console.log(json);
{"name":"Alice","age":25}
Format JSON for Readability
const formatted =
JSON.stringify(
user,
null,
2
);
console.log(formatted);
{
"name": "Alice",
"age": 25
}
JSON.parse() throws a SyntaxError when the text
is not valid JSON. Use try...catch when parsing data that may
be malformed.
Parse JSON before reading its properties. Stringify JavaScript values before storing or transmitting them as JSON text.
Summary
JSON.parse()converts JSON text to JavaScript.JSON.stringify()converts JavaScript to JSON text.- Parsed objects can be accessed with normal property syntax.
- The third
stringify()argument controls indentation. - Invalid JSON causes
JSON.parse()to throw an error.
Working with JSON Data
JSON is commonly received from APIs. After parsing the JSON string into a JavaScript object, you can access its properties just like any other object.
Read JSON Data
const json = `{
"name": "Alice",
"age": 25,
"country": "USA"
}`;
const user =
JSON.parse(json);
console.log(user.name);
console.log(user.country);
Alice
USA
Loop Through JSON Arrays
const json = `[
"HTML",
"CSS",
"JavaScript"
]`;
const skills =
JSON.parse(json);
skills.forEach((skill) => {
console.log(skill);
});
HTML
CSS
JavaScript
Typical API Workflow
| Step | Description |
|---|---|
| 1 | Receive JSON from an API. |
| 2 | Convert it using JSON.parse(). |
| 3 | Read or modify the JavaScript object. |
| 4 | Convert back with JSON.stringify() if needed. |
JSON data received from external sources may be invalid.
Wrap JSON.parse() in try...catch when parsing
untrusted data.
Parse JSON once, then work with the resulting JavaScript object.
Avoid repeatedly calling JSON.parse() on the same data.
Summary
- Parse JSON before accessing its values.
- Read properties like any JavaScript object.
- Loop through JSON arrays using array methods.
- Use
try...catchfor untrusted JSON. - Stringify objects before sending or storing them.
JSON Quick Reference
Use this reference to quickly find the most common JSON syntax, conversion methods, and best practices.
| Task | Method / Syntax |
|---|---|
| Convert JSON to JavaScript | JSON.parse() |
| Convert JavaScript to JSON | JSON.stringify() |
| Pretty-print JSON | JSON.stringify(value, null, 2) |
| Access object property | object.property |
| Access array item | array[index] |
| Loop through an array | forEach() |
| Handle invalid JSON | try...catch |
| JSON object | {"name":"Alice"} |
| JSON array | ["HTML","CSS"] |
| JSON values | String • Number • Boolean • Object • Array • null |
JSON is the standard format for exchanging data between web applications
and APIs. Most modern JavaScript applications use
JSON.parse() and JSON.stringify() regularly.
JSON Chapter Complete
- JSON is a lightweight text-based data format.
JSON.parse()converts JSON into JavaScript.JSON.stringify()converts JavaScript into JSON.- Use JSON for storing and exchanging structured data.
- Handle invalid JSON with
try...catch.
Introduction to JavaScript Modules
JavaScript modules allow you to split your code into separate files. This makes applications easier to organize, reuse, and maintain by importing only the functionality you need.
Why Use Modules?
| Benefit | Description |
|---|---|
| Organization | Split large projects into smaller files. |
| Reusability | Import the same code wherever it is needed. |
| Maintainability | Keep related code together and easier to update. |
| Encapsulation | Expose only what other files need to access. |
Example Project Structure
project/
│
├── index.html
├── app.js
└── utils.js
Load a Module
<script type="module" src="app.js"></script>
Divide your application into small, focused modules. A module should have one clear responsibility instead of containing unrelated code.
Summary
- Modules split code into separate files.
- They improve organization and reusability.
- Load modules using
type="module". - Each module has its own scope.
- Use
exportandimportto share code.
Exporting JavaScript Code
The export keyword makes variables, functions, and classes
available to other modules. JavaScript supports named exports and one
default export per file.
Named Exports
export const appName =
"CheatSheetSilo";
export function add(a, b) {
return a + b;
}
export class User {
constructor(name) {
this.name = name;
}
}
Export After Declaration
const taxRate = 0.25;
function calculateTax(price) {
return price * taxRate;
}
export {
taxRate,
calculateTax
};
Default Export
export default function formatPrice(
price
) {
return `$${price.toFixed(2)}`;
}
Named vs Default Exports
| Export Type | Limit | Typical Use |
|---|---|---|
| Named export | Multiple per file | Related functions, constants, or classes |
| Default export | One per file | The module's main value |
A module may contain many named exports but only one
export default.
Prefer named exports when a file exposes several related values. Use a default export when the file clearly represents one primary value.
Summary
exportshares code with other modules.- Variables, functions, and classes can be exported.
- Named exports allow multiple exports per file.
- A module can have only one default export.
- Exports can be declared inline or grouped at the end.
Importing JavaScript Code
The import statement loads exported values from another
module. Import syntax depends on whether the value was exported as a
named or default export.
Import Named Exports
import {
appName,
add,
User
} from "./utils.js";
console.log(appName);
console.log(add(2, 3));
const user =
new User("Alice");
Import a Default Export
import formatPrice
from "./formatter.js";
console.log(
formatPrice(19.99)
);
Rename an Import
import {
calculateTax as getTax
} from "./utils.js";
console.log(
getTax(100)
);
Import Syntax
| Import Type | Syntax |
|---|---|
| Named import | import { name } from "./file.js" |
| Default import | import name from "./file.js" |
| Renamed import | import { name as alias } from "./file.js" |
| All named exports | import * as utils from "./file.js" |
Browser modules normally require relative paths such as
./utils.js and the file extension should be included.
Import only the values a file needs. Clear, specific imports make module dependencies easier to understand.
Summary
- Named imports use curly braces.
- Default imports do not use curly braces.
- Named imports must match their exported names.
- Use
asto rename an imported value. - Relative module paths commonly begin with
./.
Named vs Default Exports
JavaScript modules support two export styles. Named exports allow multiple values to be shared, while a default export represents the module's primary value.
Comparison
| Feature | Named Export | Default Export |
|---|---|---|
| Exports per file | Multiple | One |
| Curly braces | Required when importing | Not used |
| Import name | Must match the exported name | Can be any valid identifier |
| Typical use | Utility functions and constants | Main class or primary function |
Named Export
// utils.js
export function add(a, b) {
return a + b;
}
// app.js
import { add }
from "./utils.js";
Default Export
// formatter.js
export default
function format() {
return "Done";
}
// app.js
import format
from "./formatter.js";
Use named exports for utility modules that expose several functions. Use a default export when the module represents a single primary feature.
Summary
- Named exports allow multiple exports per file.
- Default exports allow one primary export.
- Named imports require curly braces.
- Default imports do not use curly braces.
- Choose the export style that best matches the module's purpose.
Modules Quick Reference
Use this reference to quickly find the most common JavaScript module syntax for exporting and importing code.
| Task | Syntax |
|---|---|
| Load a module | <script type="module"> |
| Export a variable | export const value |
| Export a function | export function name() |
| Export a class | export class Name |
| Default export | export default |
| Named import | import { name } from "./file.js" |
| Default import | import name from "./file.js" |
| Rename an import | import { name as alias } |
| Import everything | import * as utils from "./file.js" |
| Relative module path | ./module.js |
Modern JavaScript projects organize code into small modules. Named exports are ideal for utility functions, while default exports are best for a module's primary feature.
Modules Chapter Complete
- Modules split code into reusable files.
- Use
exportto share values. - Use
importto access exported values. - Named exports allow multiple exports per file.
- Default exports provide one primary exported value.
Introduction to JavaScript Error Handling
Errors occur when JavaScript encounters unexpected situations during execution. Proper error handling helps prevent applications from crashing and provides a better experience for users.
Common Error Types
| Error Type | Description |
|---|---|
SyntaxError |
Invalid JavaScript syntax. |
ReferenceError |
Using a variable that doesn't exist. |
TypeError |
Calling a method on an incompatible value. |
RangeError |
A value is outside the allowed range. |
Error |
The generic JavaScript error object. |
Simple Example
console.log(userName);
ReferenceError:
userName is not defined
An uncaught error usually stops execution of the current script. Handle expected errors whenever your application depends on external data, user input, or network requests.
Don't ignore errors. Catch them when appropriate, log useful information, and display friendly messages to users instead of exposing raw error details.
Summary
- Errors occur during code execution.
- JavaScript provides several built-in error types.
- Uncaught errors can stop script execution.
- Error handling improves application reliability.
- The next step is learning
try,catchandfinally.
Using try, catch and finally
The try, catch, and
finally statements allow you to handle runtime errors
without stopping your application.
Syntax
try {
// Code that may fail
} catch (error) {
// Handle the error
} finally {
// Always runs
}
Catch an Error
try {
console.log(userName);
} catch (error) {
console.log(
"Something went wrong."
);
}
Something went wrong.
Using finally
try {
console.log("Loading...");
} finally {
console.log("Finished");
}
Loading...
Finished
Statements Overview
| Statement | Purpose |
|---|---|
try |
Runs code that might throw an error. |
catch |
Handles the thrown error. |
finally |
Always runs after try and catch. |
Use finally for cleanup tasks such as hiding loading
indicators or closing resources. Handle only the errors you expect.
Summary
tryexecutes code that may fail.catchhandles runtime errors.finallyalways executes.- Applications continue running after handled errors.
- Use clear error messages for easier debugging.
Throwing Custom Errors
Use the throw statement to create your own errors when
invalid data or unexpected situations occur. Custom errors make bugs
easier to detect and handle.
Throw an Error
const age = -1;
if (age < 0) {
throw new Error(
"Age cannot be negative."
);
}
Error:
Age cannot be negative.
Throw and Catch
try {
throw new Error(
"Something went wrong."
);
} catch (error) {
console.log(
error.message
);
}
Something went wrong.
When to Use throw
| Situation | Recommendation |
|---|---|
| Invalid function arguments | Throw an Error. |
| Unexpected application state | Throw an Error. |
| Recoverable user mistakes | Usually validate instead of throwing. |
Throw errors only for exceptional situations. Use clear, descriptive messages that help identify the problem quickly.
Summary
throwcreates a custom error.new Error()creates a standard Error object.- Thrown errors can be caught with
try...catch. - Write descriptive error messages.
- Reserve exceptions for truly exceptional conditions.
Error Handling Quick Reference
Use this reference to quickly find the most common JavaScript error handling statements, methods, and built-in error types.
| Task | Statement / Object |
|---|---|
| Handle errors | try...catch |
| Always execute code | finally |
| Throw an error | throw |
| Create an Error object | new Error() |
| Error message | error.message |
| Error name | error.name |
| Generic error | Error |
| Invalid syntax | SyntaxError |
| Undefined variable | ReferenceError |
| Invalid value type | TypeError |
| Value out of range | RangeError |
Handle expected errors with
try...catch, throw meaningful errors when necessary, and
avoid silently ignoring exceptions.
Error Handling Chapter Complete
- Use
try...catchto handle runtime errors. finallyalways executes after the try/catch block.- Create custom errors with
throw new Error(). - Use descriptive error messages for easier debugging.
- Handle errors gracefully to improve application reliability.
JavaScript Cheat Sheet Resources
Use these authoritative references to verify language behavior and continue learning modern JavaScript:
Official JavaScript Documentation
Related Programming Cheat Sheets
Frequently Asked Questions
Find answers to the most common questions about JavaScript, learning the language, and using this cheat sheet as a quick reference.
What is JavaScript?
JavaScript is a programming language used to build interactive websites and web applications. It runs in all modern browsers and is one of the core technologies of the web alongside HTML and CSS.
What is a JavaScript cheat sheet?
A JavaScript cheat sheet is a quick reference guide containing common syntax, methods, keywords, and practical examples. It helps you find information quickly without reading long documentation.
Is JavaScript easy to learn?
JavaScript is beginner-friendly, especially if you already know HTML and CSS. While advanced concepts take time to master, you can build useful projects after learning the fundamentals.
Should I learn JavaScript before React?
Yes. React relies heavily on JavaScript concepts such as functions, objects, arrays, modules, events, and asynchronous programming. Learning JavaScript first makes React much easier.
Is JavaScript the same as ECMAScript?
No. ECMAScript is the official specification, while JavaScript is the programming language that implements that specification. Modern JavaScript follows the ECMAScript standard.
Can JavaScript be used for backend development?
Yes. With Node.js, JavaScript can be used to build servers, APIs, command-line tools, and full-stack applications.
What is the difference between let, const and var?
let creates block-scoped variables that can be reassigned,
const creates block-scoped variables that cannot be
reassigned, and var uses function scope and is generally
avoided in modern JavaScript.
How do I debug JavaScript code?
Use your browser's Developer Tools (F12), the Console, breakpoints,
and console.log() to inspect variables, trace execution,
and identify errors.
What are JavaScript modules?
Modules split code into separate files using
export and import. They make applications
easier to organize, maintain, and reuse.
What is JSON used for?
JSON (JavaScript Object Notation) is a lightweight format for storing and exchanging structured data. It is commonly used by web APIs and modern applications.
What is asynchronous JavaScript?
Asynchronous JavaScript allows tasks such as network requests and
timers to run without blocking the rest of the application. Common
tools include Promises and async/await.
Is this JavaScript Cheat Sheet free?
Yes. This cheat sheet is completely free to use and is designed as a practical reference for beginners, students, and professional developers.