JavaScript Cheat Sheet – Complete JavaScript Syntax & Reference

JavaScript Beginner Friendly Interactive Reference

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.

Last updated: July 2026 Examples: 100+ Sections: 20+

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.

QUICK REFERENCE

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!");
Pro Tip: Open the browser developer tools with F12 or Ctrl + Shift + I to view messages printed with console.log().
VARIABLES

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
Tip: Although 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.

JavaScript
let age = 25;

age = 26;

console.log(age);
Output
26

Using const

Use const for values that should never be reassigned.

JavaScript
const pi = 3.14159;

console.log(pi);
Output
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.

JavaScript
var city = "London";

var city = "Paris";

console.log(city);
Output
Paris
Warning: Unlike 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.

JavaScript
if (true) {

  let message = "Hello";

  console.log(message);

}

// console.log(message);
Output
Hello

Function Scope

Variables declared with var are limited to the function in which they are declared.

JavaScript
function demo() {

  var score = 100;

  console.log(score);

}

demo();
Output
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.

JavaScript
console.log(age);

let age = 20;
Result
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.

JavaScript
const user = {

  name: "Alice"

};

user.name = "Bob";

console.log(user.name);
Output
Bob
Remember: 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.
Interview Question: Why is 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.

DATA TYPES

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" ⚠️
Important: Although 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.

JavaScript
console.log(typeof "Hello");
console.log(typeof 42);
console.log(typeof true);
console.log(typeof undefined);
Output
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.

JavaScript
const user = {
  name: "Alice",
  age: 30,
  active: true
};

console.log(user.name);
Output
Alice

Arrays

Arrays are special objects used to store ordered collections of values.

JavaScript
const colors = ["red", "green", "blue"];

console.log(colors[0]);
console.log(colors.length);
Output
red
3

Functions are Objects

Functions are callable objects. They can be assigned to variables, passed as arguments and returned from other functions.

JavaScript
function greet() {
  return "Hello";
}

const sayHello = greet;

console.log(sayHello());
Output
Hello

Copy by Value

Primitive values are copied by value. Changing one variable does not affect the other.

JavaScript
let first = 10;
let second = first;

second = 20;

console.log(first);
console.log(second);
Output
10
20

Copy by Reference

Objects are assigned by reference. Two variables can point to the same object in memory.

JavaScript
const firstUser = {
  name: "Alice"
};

const secondUser = firstUser;

secondUser.name = "Bob";

console.log(firstUser.name);
console.log(secondUser.name);
Output
Bob
Bob
Common Mistake: Assigning an object or array to another variable does not create a separate copy. Both variables still reference the same object.

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
Pro Tip: Use 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")
JavaScript
const ageText = "30";
const ageNumber = Number(ageText);

console.log(ageNumber);
console.log(typeof ageNumber);
Output
30
number

Implicit Type Coercion

JavaScript sometimes converts values automatically. This is called type coercion.

JavaScript
console.log("5" + 2);
console.log("5" - 2);
console.log(true + 1);
Output
52
3
2
Common Mistake: The + 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
JavaScript
const username = "";

if (username) {
  console.log("Username exists");
} else {
  console.log("Username is missing");
}
Output
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
Best Practice: Prefer === and !== because their behavior is more predictable.

NaN

NaN means “Not a Number”. It usually appears when a numeric conversion or calculation fails.

JavaScript
const result = Number("hello");

console.log(result);
console.log(Number.isNaN(result));
Output
NaN
true
Important: 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.
Interview Question: What is the difference between primitive and reference types in JavaScript?

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.

Quick overview

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

JavaScript
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);
Output
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.

JavaScript
const number = 17;

const isEven = number % 2 === 0;
const isOdd = number % 2 !== 0;

console.log(isEven);
console.log(isOdd);
console.log(17 % 5);
Output
false
true
2
Practical use

Use index % array.length when you need an index to wrap back to the beginning of an array.

JavaScript
const colors = ["red", "green", "blue"];

for (let index = 0; index < 7; index++) {
  const color = colors[index % colors.length];
  console.log(color);
}
Output
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().

JavaScript
const square = 5 ** 2;
const cube = 4 ** 3;
const squareRoot = 81 ** 0.5;

console.log(square);
console.log(cube);
console.log(squareRoot);
Output
25
64
9
Modern best practice

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
JavaScript
let firstValue = 5;
let secondValue = 5;

const postfixResult = firstValue++;
const prefixResult = ++secondValue;

console.log(postfixResult);
console.log(firstValue);

console.log(prefixResult);
console.log(secondValue);
Output
5
6
6
6
Avoid hidden side effects

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.

JavaScript
// 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.

JavaScript
const numericString = "42";
const decimalString = "19.95";
const invalidNumber = "JavaScript";

console.log(+numericString);
console.log(+decimalString);
console.log(+invalidNumber);
console.log(-numericString);
Output
42
19.95
NaN
-42
Be careful with automatic conversion

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.

JavaScript
console.log(10 + 5);
console.log("10" + 5);
console.log(10 + "5");
console.log(10 + 5 + "px");
console.log("Total: " + 10 + 5);
Output
15
105
105
15px
Total: 105
Common JavaScript mistake

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.

JavaScript
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);
Output
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.

JavaScript
console.log(10 / 0);
console.log(-10 / 0);
console.log(0 / 0);
console.log("hello" * 5);
Output
Infinity
-Infinity
NaN
NaN
Validate numeric results

Use Number.isFinite() when a calculation must produce a normal finite number. Use Number.isNaN() when checking specifically for NaN.

JavaScript
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));
Output
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.

JavaScript
const result = 0.1 + 0.2;

console.log(result);
console.log(result === 0.3);
Output
0.30000000000000004
false
Working with money

For simple currency calculations, store amounts in the smallest unit, such as cents, rather than using decimal values directly.

JavaScript
const firstPriceInCents = 10;
const secondPriceInCents = 20;

const totalInCents = firstPriceInCents + secondPriceInCents;
const totalInDollars = totalInCents / 100;

console.log(totalInDollars);
Output
0.3
Arithmetic operators summary
  • 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 Infinity or NaN.
  • Be aware of floating-point precision when working with decimal values.
Interview question

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.

JavaScript
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.

JavaScript
let total = 20;

total += 5;
total *= 2;
total -= 10;
total /= 3;

console.log(total);
13.333333333333334
Best Practice

Use compound assignment operators whenever you are updating an existing variable. They are concise, widely recognized, and make your intent immediately clear.

Common Mistake

Compound assignment does not change JavaScript’s type coercion rules. For example, using += with strings performs string concatenation instead of numeric addition.

JavaScript
let value = "10";

value += 5;

console.log(value);
105
Quick Summary
  • 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).

JavaScript
let username = "";

username ||= "Guest";

console.log(username);
Guest

AND Assignment (&&=)

The &&= operator assigns a new value only when the current value is truthy.

JavaScript
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.

JavaScript
let theme = null;
let age = 0;

theme ??= "Light";
age ??= 18;

console.log(theme);
console.log(age);
Light
0
Best Practice

Prefer ??= when assigning default values to user input or configuration settings. It avoids accidentally replacing valid values like 0, false, or an empty string.

Common Pitfall

Do not confuse ||= with ??=. If 0 or "" are valid values, use ??= to avoid overwriting them.

Quick Summary
  • ||= assigns when the value is falsy.
  • &&= assigns when the value is truthy.
  • ??= assigns only when the value is null or undefined.
  • ??= 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.

JavaScript
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.

JavaScript
const score = 92;

const passed = score >= 50;
const perfect = score === 100;

console.log(passed);
console.log(perfect);
true
false
Best Practice

Write comparisons so they read naturally. Clear conditions improve readability and make your code easier to maintain.

Quick Summary
  • Comparison operators always return true or false.
  • 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.

JavaScript
console.log(5 == "5");
console.log(false == 0);
console.log(null == undefined);
true
true
true
Why this can be dangerous

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.

JavaScript
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.

JavaScript
const userAge = "18";

if (Number(userAge) === 18) {
    console.log("Adult");
}
Adult
Modern Best Practice

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.

Quick Summary
  • == compares values after type conversion.
  • === compares both value and data type.
  • !== is generally preferred over !=.
  • Modern JavaScript code should almost always use ===.
Interview Question

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.

JavaScript
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.

JavaScript
const score = 78;

if (score >= 50) {
    console.log("You passed the exam.");
} else {
    console.log("You failed the exam.");
}
You passed the exam.
Best Practice

Use relational operators together with meaningful variable names. Expressions like score >= passingScore are easier to understand than comparing against unexplained numbers.

Quick Summary
  • > 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.

JavaScript
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.

JavaScript
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.

JavaScript
const isLoggedIn = false;

console.log(!isLoggedIn);
true
Best Practice

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.

Quick Summary
  • && 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.

Why it matters

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).

JavaScript
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.

JavaScript
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
false
0
""
null
undefined
NaN
All other values are considered truthy, including objects, arrays, non-empty strings, and non-zero numbers.
JavaScript
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
Common Pitfall

Empty arrays ([]) and empty objects ({}) are truthy in JavaScript. This often surprises beginners who expect them to behave like empty strings or zero.

Quick Summary
  • && 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.

JavaScript
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.

JavaScript
// Ternary
const status = score >= 50
    ? "Pass"
    : "Fail";

// if...else
let result;

if (score >= 50) {
    result = "Pass";
} else {
    result = "Fail";
}
Best Practice

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.

Avoid Nested Ternaries

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.

JavaScript
// Avoid
const grade =
    score >= 90 ? "A" :
    score >= 80 ? "B" :
    score >= 70 ? "C" :
    "F";
Quick Summary
  • 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.

JavaScript
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.

JavaScript
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 ""
Best Practice

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.

Common Mistake

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.

Quick Summary
  • ?? only falls back for null and undefined.
  • || falls back for all falsy values.
  • Use ?? when you want to preserve valid values like 0 and false.

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.

JavaScript
const user = null;

// Throws an error
console.log(user.name);
Runtime Error

Accessing a property on null or undefined throws a TypeError.

Using Optional Chaining

Optional chaining safely returns undefined instead of throwing an error.

JavaScript
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.

JavaScript
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.

JavaScript
const user = {};

user.sayHello?.();
// No error
Real-World Usage

Optional chaining is commonly used when working with REST APIs, GraphQL responses, browser APIs, and third-party libraries where some properties may be missing.

Best Practice

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.

Quick Summary
  • Use ?. to safely access properties.
  • It returns undefined instead 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.

JavaScript
console.log(typeof 42);
console.log(typeof "Hello");
console.log(typeof true);
console.log(typeof undefined);
console.log(typeof {});
number
string
boolean
undefined
object
Common Use Cases
  • Validate function arguments.
  • Inspect API responses.
  • Debug unexpected values.
  • Perform runtime type checks.
Important Exception

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.

JavaScript
console.log(typeof null);
object
Quick Summary
  • typeof always returns a string.
  • It is ideal for runtime type checking.
  • typeof null returns "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.

JavaScript
const today = new Date();
const items = [];

console.log(today instanceof Date);
console.log(items instanceof Array);
console.log(items instanceof Object);
true
true
true
Best Practice

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.

JavaScript
const user = {
    name: "Maya",
    email: undefined
};

console.log("name" in user);
console.log("email" in user);
console.log("age" in user);
true
true
false
Property Existence vs Value

Checking object.property !== undefined is not the same as checking whether the property exists. A property can exist while intentionally storing undefined.

JavaScript
const settings = {
    theme: undefined
};

console.log("theme" in settings);
console.log(settings.theme !== undefined);
true
false
Own Properties Only

The in operator also detects inherited properties. Use Object.hasOwn(object, property) when you only want to check properties defined directly on the object.

JavaScript
const user = {
    name: "Maya"
};

console.log("toString" in user);
console.log(Object.hasOwn(user, "toString"));
console.log(Object.hasOwn(user, "name"));
true
false
true
Quick Summary
  • instanceof checks prototype relationships.
  • Prefer Array.isArray() when checking arrays.
  • in checks 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.

JavaScript
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.

JavaScript
console.log(8 << 1);
console.log(8 >> 1);
16
4
When Are Bitwise Operators Used?
  • Permission and flag systems.
  • Image and graphics processing.
  • Compression algorithms.
  • Game development.
  • Low-level data manipulation.
Modern JavaScript

Most web applications rarely require bitwise operators. They are considered an advanced topic and are primarily used in specialized programming scenarios.

Quick Summary
  • 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.

Why It Matters

Understanding operator precedence helps you avoid subtle bugs and makes complex expressions easier to read and maintain.

Basic Example

JavaScript
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.

JavaScript
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
Don't Memorize Everything

Most experienced JavaScript developers do not memorize the complete precedence table. Instead, they use parentheses whenever an expression could be misunderstood.

Best Practice

Even if you know the precedence rules, adding parentheses often improves readability and makes your intentions clear to other developers.

Quick Summary
  • 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.
Equality Recommendation

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
Use Special Operators Carefully

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.

Quick Reference Summary
  • Use ?. for safe property access.
  • Use ?? for nullish fallback values.
  • Use typeof, instanceof, and in for 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.

Recommended
if (userAge === 18) {
    console.log("Adult");
}
Why?

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.

Avoid This
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.

Modern JavaScript

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.

Best Practices Checklist
  • ✔ 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.

Incorrect
console.log(5 == "5");
true
Recommended
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.

Unexpected Result
const quantity = 0;

console.log(quantity || 10);
10
Better Alternative
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
Clearer Code
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
Remember

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";
Better Approach

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
Common Mistakes Checklist
  • 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 null returns "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.

Key idea: A conditional statement does not simply check a value. It evaluates an expression and then decides which code path should run.

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";
Important: Use the ternary operator for short value assignments, not for large or deeply nested control-flow structures. Complex decisions are usually more readable with 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.

JavaScript
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);
Output
Welcome back, Maya.

How JavaScript Evaluates the Conditions

  1. JavaScript first evaluates !user.isLoggedIn.
  2. Because user.isLoggedIn is true, the negated expression becomes false.
  3. JavaScript skips the first block and checks user.isAdmin.
  4. Because user.isAdmin is also false, JavaScript skips the second block.
  5. The final else block runs because none of the previous conditions matched.
Order matters: In an 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 if statements
  • Choosing between if...else execution 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.

Syntax
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.

Best practice: Always use curly braces, even when the conditional block contains only one statement. This makes the code easier to maintain and prevents bugs when additional statements are added later.

Basic if Statement Example

This example displays a notification only when the number of unread messages is greater than zero.

Check a Numeric Condition
const unreadMessages = 3;

if (unreadMessages > 0) {
  console.log(`You have ${unreadMessages} unread messages.`);
}
Output
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.

Boolean Condition
const isEmailVerified = true;

if (isEmailVerified) {
  console.log("Your email address is verified.");
}
Output
Your email address is verified.
Avoid unnecessary comparisons: Write 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
Multiple Requirements
const user = {
  isLoggedIn: true,
  hasSubscription: true,
  accountSuspended: false
};

if (
  user.isLoggedIn &&
  user.hasSubscription &&
  !user.accountSuspended
) {
  console.log("Premium content unlocked.");
}
Output
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:

  • false
  • 0 and -0
  • 0n
  • "", '', and empty template strings
  • null
  • undefined
  • NaN

Nearly every other JavaScript value is truthy, including empty arrays and empty objects.

Truthy String Check
const username = "Avery";

if (username) {
  console.log(`Signed in as ${username}.`);
}
Output
Signed in as Avery.
Important: Empty arrays and empty objects are truthy. Therefore, 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.

Explicit Undefined Check
const product = {
  name: "JavaScript Course",
  discountPercent: 0
};

if (product.discountPercent !== undefined) {
  console.log(`Discount: ${product.discountPercent}%`);
}
Output
Discount: 0%
Choose conditions intentionally: Use a truthiness check when you genuinely want to reject every falsy value. Use an explicit comparison when values such as 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.
Creating Strings
const first = "JavaScript";
const second = 'Cheat Sheet';
const third = `Learn ${first}`;

console.log(first);
console.log(second);
console.log(third);
Output
JavaScript
Cheat Sheet
Learn JavaScript

Strings Are Immutable

Individual characters cannot be changed directly. You must create and assign a new string.

String Immutability
let language = "JavaScript";

language[0] = "X";

console.log(language);

language = "TypeScript";

console.log(language);
Output
JavaScript
TypeScript
Remember

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.

length Property
const language = "JavaScript";

console.log(language.length);
Output
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
Character Access
const word = "JavaScript";

console.log(word[0]);
console.log(word.charAt(4));
console.log(word.at(-1));
Output
J
S
t

Template Literals

Template literals use backticks and allow expressions to be inserted with ${expression}.

String Interpolation
const name = "Alice";
const lessons = 12;

const message =
  `${name} completed ${lessons} lessons.`;

console.log(message);
Output
Alice completed 12 lessons.

Multiline Strings

Template literals can span multiple lines without escape sequences.

Multiline Template Literal
const message = `JavaScript
Strings
Cheat Sheet`;

console.log(message);
Output
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.

String Concatenation
const firstName = "Alice";
const lastName = "Johnson";

const fullName =
  firstName + " " + lastName;

const modernName =
  `${firstName} ${lastName}`;

console.log(fullName);
console.log(modernName);
Output
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
Letter Case
const text = "JavaScript";

console.log(text.toUpperCase());
console.log(text.toLowerCase());
console.log(text);
Output
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.
trim()
const input = "   Alice   ";

console.log(input);
console.log(input.trim());
Output
   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
Search Methods
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")
);
Output
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.
slice()
const language = "JavaScript";

console.log(
  language.slice(0, 4)
);

console.log(
  language.slice(4)
);

console.log(
  language.slice(-6)
);
Output
Java
Script
Script

Replacing Text

Use replace() for one match and replaceAll() for every matching substring.

replace() and replaceAll()
const sentence =
  "JavaScript is fun. JavaScript is useful.";

console.log(
  sentence.replace(
    "JavaScript",
    "TypeScript"
  )
);

console.log(
  sentence.replaceAll(
    "JavaScript",
    "TypeScript"
  )
);
Output
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")
Repeat and Padding
console.log(
  "-".repeat(10)
);

console.log(
  "7".padStart(3, "0")
);

console.log(
  "JS".padEnd(5, ".")
);
Output
----------
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.
Common Mistake

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"
Best Practice

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, or at() to inspect characters.
  • Use search methods to locate text.
  • Use slice() to extract substrings.
  • Use replace() or replaceAll() 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.

split()
const languages =
  "JavaScript,Python,SQL";

const result =
  languages.split(",");

console.log(result);
Output
["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().

split(), map(), and join()
const title =
  "javascript string methods";

const formatted = title
  .split(" ")
  .map(word =>
    word[0].toUpperCase() +
    word.slice(1)
  )
  .join(" ");

console.log(formatted);
Output
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.
match()
const text =
  "Order 12 contains 3 products";

const numbers =
  text.match(/\d+/g);

console.log(numbers);
Output
["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.

matchAll()
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
  );
}
Output
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

Regular Expression Search
const text =
  "Learn JavaScript";

console.log(
  /javascript/i.test(text)
);

console.log(
  text.search(/javascript/i)
);
Output
true
6

Replacing Text with Regular Expressions

Regular expressions make it possible to replace patterns rather than exact text values.

Pattern Replacement
const phone =
  "555 123 4567";

const formatted =
  phone.replace(
    /\s+/g,
    "-"
  );

console.log(formatted);
Output
555-123-4567

Replacement Functions

The second argument to replace() may be a function. The function receives the matched text and returns its replacement.

Replacement Callback
const text =
  "Products: 5, price: 20";

const result =
  text.replace(
    /\d+/g,
    match =>
      String(Number(match) * 2)
  );

console.log(result);
Output
Products: 10, price: 40

Comparing Strings

Equality operators compare string values exactly, including capitalization and whitespace.

Strict String Comparison
console.log(
  "JavaScript" === "JavaScript"
);

console.log(
  "JavaScript" === "javascript"
);

console.log(
  "Hello" === "Hello "
);
Output
true
false
false

Case-Insensitive Comparison

Normalize both values before comparing them.

Normalized Comparison
const first =
  " JavaScript ";

const second =
  "javascript";

const isEqual =
  first.trim().toLowerCase() ===
  second.trim().toLowerCase();

console.log(isEqual);
Output
true

Locale-Aware Comparison

The localeCompare() method compares strings according to language-specific sorting rules.

localeCompare()
const names = [
  "Östen",
  "Anna",
  "Åke",
  "Älva"
];

names.sort((first, second) =>
  first.localeCompare(
    second,
    "sv"
  )
);

console.log(names);
Output
["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.
Do Not Expect Exactly -1 or 1

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.
Explicit Conversion
const number = 42;
const active = true;
const missing = null;

console.log(
  String(number)
);

console.log(
  String(active)
);

console.log(
  String(missing)
);
Output
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
Escape Characters
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);
Output
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.

String.raw
const path =
  String.raw`C:\Users\Alice`;

console.log(path);
Output
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.

Emoji Length
const emoji = "😊";

console.log(
  emoji.length
);

console.log(
  [...emoji].length
);
Output
2
1
Unicode-Aware Iteration

Spread syntax, Array.from(), and for...of iterate by Unicode code point and handle many emoji more accurately than split("").

Iterating Over a String

for...of
const text = "A😊B";

for (const character of text) {
  console.log(character);
}
Output
A
😊
B

Unicode Code Points

codePointAt() and fromCodePoint()
const emoji = "😊";

const codePoint =
  emoji.codePointAt(0);

console.log(codePoint);

console.log(
  String.fromCodePoint(codePoint)
);
Output
128522
😊

Unicode Normalization

Visually identical text may use different Unicode sequences. The normalize() method converts them to a consistent form.

normalize()
const first = "\u00E9";
const second = "e\u0301";

console.log(
  first === second
);

console.log(
  first.normalize() ===
  second.normalize()
);
Output
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.

Tagged Template
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);
Output
[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.

Primitive vs Object
const primitive =
  "JavaScript";

const object =
  new String("JavaScript");

console.log(
  typeof primitive
);

console.log(
  typeof object
);

console.log(
  primitive === object
);
Output
string
object
false
Avoid String Objects

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.

Slug Generator
function createSlug(title) {
  return title
    .trim()
    .toLowerCase()
    .replace(
      /[^a-z0-9]+/g,
      "-"
    )
    .replace(
      /^-|-$/g,
      ""
    );
}

console.log(
  createSlug(
    " JavaScript String Methods! "
  )
);
Output
javascript-string-methods

Practical Pattern: Mask Sensitive Text

Mask a Card Number
function maskCard(number) {
  const text =
    String(number);

  return text
    .slice(-4)
    .padStart(
      text.length,
      "*"
    );
}

console.log(
  maskCard("1234567812345678")
);
Output
************5678

Practical Pattern: Count Words

Word Counter
function countWords(text) {
  const cleaned =
    text.trim();

  if (!cleaned) {
    return 0;
  }

  return cleaned
    .split(/\s+/)
    .length;
}

console.log(
  countWords(
    "Learn modern JavaScript today"
  )
);
Output
4

Practical Pattern: Truncate Text

Text Truncation
function truncate(
  text,
  maximumLength
) {
  if (
    text.length <=
    maximumLength
  ) {
    return text;
  }

  return (
    text.slice(
      0,
      maximumLength - 3
    ) + "..."
  );
}

console.log(
  truncate(
    "JavaScript string methods",
    18
  )
);
Output
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

Recommended 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.
Security Reminder

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.

Array Literal
const languages = [
  "JavaScript",
  "Python",
  "SQL"
];

console.log(languages);
Output
["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.
Constructor Difference

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.

Mixed Array
const mixedValues = [
  "JavaScript",
  42,
  true,
  null,
  { level: "Beginner" },
  ["HTML", "CSS"]
];

console.log(mixedValues);
Best Practice

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
Accessing Elements
const languages = [
  "JavaScript",
  "Python",
  "SQL"
];

console.log(languages[0]);
console.log(languages[1]);
console.log(languages[2]);
Output
JavaScript
Python
SQL

Accessing a Missing Index

Reading an index that does not exist returns undefined.

Missing Element
const colors = [
  "red",
  "green",
  "blue"
];

console.log(colors[10]);
Output
undefined

The length Property

The length property returns the number of positions in an array.

Array Length
const languages = [
  "JavaScript",
  "Python",
  "SQL"
];

console.log(
  languages.length
);
Output
3

Accessing the Last Element

Subtract one from length or use at(-1) to retrieve the final element.

Last Array Element
const languages = [
  "JavaScript",
  "Python",
  "SQL"
];

console.log(
  languages[
    languages.length - 1
  ]
);

console.log(
  languages.at(-1)
);
Output
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.

Update an Element
const languages = [
  "JavaScript",
  "Python",
  "SQL"
];

languages[1] =
  "TypeScript";

console.log(languages);
Output
["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 Array
const colors = [
  "red",
  "green"
];

colors[0] = "blue";

console.log(colors);
Output
["blue", "green"]
Reassignment Is Different
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.

Add with length
const languages = [
  "JavaScript",
  "Python"
];

languages[
  languages.length
] = "SQL";

console.log(languages);
Output
["JavaScript", "Python", "SQL"]

Avoid Creating Empty Slots

Assigning a value far beyond the current length creates empty positions in the array.

Sparse Array
const values = [
  "A",
  "B"
];

values[5] = "F";

console.log(values);
console.log(values.length);
Output
["A", "B", empty × 3, "F"]
6
Sparse Arrays

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.

Array.isArray()
const languages = [
  "JavaScript",
  "Python"
];

console.log(
  Array.isArray(languages)
);

console.log(
  typeof languages
);
Output
true
object

Creating Arrays with Array.from()

The Array.from() method converts iterable and array-like values into real arrays.

Array.from()
const characters =
  Array.from("JavaScript");

console.log(characters);
Output
["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.

Create a Number Sequence
const numbers =
  Array.from(
    { length: 5 },
    (_, index) =>
      index + 1
  );

console.log(numbers);
Output
[1, 2, 3, 4, 5]

Copying an Array

Spread syntax and slice() create a shallow copy of an array.

Shallow Array Copies
const original = [
  "JavaScript",
  "Python"
];

const spreadCopy = [
  ...original
];

const sliceCopy =
  original.slice();

spreadCopy.push("SQL");

console.log(original);
console.log(spreadCopy);
console.log(sliceCopy);
Output
["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.

Nested References
const original = [
  {
    name: "Alice"
  }
];

const copy = [
  ...original
];

copy[0].name = "Maya";

console.log(
  original[0].name
);

console.log(
  copy[0].name
);
Output
Maya
Maya
Shallow Does Not Mean Independent

Spread syntax copies only the outer array. Use structuredClone() when a supported value requires a deeper independent copy.

Combining Arrays with Spread Syntax

Merge Arrays
const frontend = [
  "HTML",
  "CSS"
];

const programming = [
  "JavaScript",
  "Python"
];

const skills = [
  ...frontend,
  ...programming
];

console.log(skills);
Output
["HTML", "CSS", "JavaScript", "Python"]

Nested Arrays

An array can contain other arrays, creating multidimensional structures.

Two-Dimensional Array
const grid = [
  ["A1", "A2"],
  ["B1", "B2"],
  ["C1", "C2"]
];

console.log(grid[0][1]);
console.log(grid[2][0]);
Output
A2
C1

Arrays of Objects

Real applications frequently represent records as an array of objects.

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
);
Output
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.
Best Practice

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 length property 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.

push()
const languages = [
  "JavaScript",
  "Python"
];

const newLength =
  languages.push(
    "SQL",
    "PHP"
  );

console.log(languages);
console.log(newLength);
Output
["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.

pop()
const languages = [
  "JavaScript",
  "Python",
  "SQL"
];

const removed =
  languages.pop();

console.log(removed);
console.log(languages);
Output
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.

unshift()
const queue = [
  "Second",
  "Third"
];

const newLength =
  queue.unshift("First");

console.log(queue);
console.log(newLength);
Output
["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.

shift()
const queue = [
  "First",
  "Second",
  "Third"
];

const removed =
  queue.shift();

console.log(removed);
console.log(queue);
Output
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
Performance Note

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.

Remove with splice()
const colors = [
  "red",
  "green",
  "blue",
  "yellow"
];

const removed =
  colors.splice(1, 2);

console.log(colors);
console.log(removed);
Output
["red", "yellow"]
["green", "blue"]

Inserting Elements with splice()

Use a delete count of 0 to insert values without removing anything.

Insert with splice()
const languages = [
  "JavaScript",
  "SQL"
];

languages.splice(
  1,
  0,
  "Python",
  "TypeScript"
);

console.log(languages);
Output
["JavaScript", "Python", "TypeScript", "SQL"]

Replacing Elements with splice()

Replace with splice()
const languages = [
  "JavaScript",
  "Python",
  "SQL"
];

const removed =
  languages.splice(
    1,
    1,
    "TypeScript"
  );

console.log(languages);
console.log(removed);
Output
["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.

Negative splice() Index
const values = [
  "A",
  "B",
  "C",
  "D"
];

values.splice(
  -2,
  1,
  "X"
);

console.log(values);
Output
["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.

toSpliced()
const languages = [
  "JavaScript",
  "Python",
  "SQL"
];

const updated =
  languages.toSpliced(
    1,
    1,
    "TypeScript"
  );

console.log(languages);
console.log(updated);
Output
["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.

reverse()
const numbers = [
  1,
  2,
  3,
  4
];

const reversed =
  numbers.reverse();

console.log(numbers);
console.log(reversed);
console.log(
  numbers === reversed
);
Output
[4, 3, 2, 1]
[4, 3, 2, 1]
true

Non-Mutating Alternative: toReversed()

toReversed()
const numbers = [
  1,
  2,
  3,
  4
];

const reversed =
  numbers.toReversed();

console.log(numbers);
console.log(reversed);
Output
[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.

fill()
const values = [
  1,
  2,
  3,
  4,
  5
];

values.fill(
  0,
  1,
  4
);

console.log(values);
Output
[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.

fill() Object Reference
const users =
  new Array(3).fill({
    active: false
  });

users[0].active = true;

console.log(users);
Output
[
  { active: true },
  { active: true },
  { active: true }
]
Shared Object Warning

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.

copyWithin()
const values = [
  "A",
  "B",
  "C",
  "D",
  "E"
];

values.copyWithin(
  0,
  3
);

console.log(values);
Output
["D", "E", "C", "D", "E"]

copyWithin() with a Limited Range

Limited copyWithin()
const values = [
  1,
  2,
  3,
  4,
  5
];

values.copyWithin(
  1,
  3,
  5
);

console.log(values);
Output
[1, 4, 5, 4, 5]

Changing the length Property

The length property is writable. Reducing it permanently removes elements from the end.

Truncate with length
const values = [
  "A",
  "B",
  "C",
  "D"
];

values.length = 2;

console.log(values);
Output
["A", "B"]

Increasing the length Property

Increasing length creates empty slots rather than explicit undefined values.

Increase Array Length
const values = [
  "A",
  "B"
];

values.length = 5;

console.log(values);
console.log(values.length);
Output
["A", "B", empty × 3]
5
Use length Carefully

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.

Clear an Existing Array
const firstReference = [
  1,
  2,
  3
];

const secondReference =
  firstReference;

firstReference.length = 0;

console.log(firstReference);
console.log(secondReference);
Output
[]
[]

Mutation Through Shared References

Assigning an array to another variable copies the reference, not the array itself.

Shared Array Reference
const original = [
  "JavaScript",
  "Python"
];

const shared =
  original;

shared.push("SQL");

console.log(original);
console.log(shared);
Output
["JavaScript", "Python", "SQL"]
["JavaScript", "Python", "SQL"]
Avoid Accidental Mutation

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.

with()
const languages = [
  "JavaScript",
  "Python",
  "SQL"
];

const updated =
  languages.with(
    1,
    "TypeScript"
  );

console.log(languages);
console.log(updated);
Output
["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
Common Mutation Mistake

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.

Best Practice

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() and pop() modify the end of an array.
  • unshift() and shift() modify the beginning.
  • splice() inserts, removes, or replaces elements.
  • reverse(), fill(), and copyWithin() mutate the original array.
  • toSpliced(), toReversed(), and with() return new arrays.
  • Changing length can 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.
Callback Parameters
const languages = [
  "JavaScript",
  "Python",
  "SQL"
];

languages.forEach(
  (language, index, array) => {
    console.log(
      language,
      index,
      array.length
    );
  }
);
Output
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.

forEach()
const prices = [
  10,
  20,
  30
];

prices.forEach(price => {
  console.log(
    `$${price}`
  );
});
Output
$10
$20
$30
forEach() Does Not Build a New Array

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.

map()
const numbers = [
  1,
  2,
  3,
  4
];

const doubled =
  numbers.map(number =>
    number * 2
  );

console.log(numbers);
console.log(doubled);
Output
[1, 2, 3, 4]
[2, 4, 6, 8]

Transforming Objects with map()

Map Object Properties
const users = [
  {
    id: 1,
    name: "Alice"
  },
  {
    id: 2,
    name: "Bob"
  }
];

const names =
  users.map(user =>
    user.name
  );

console.log(names);
Output
["Alice", "Bob"]

Returning Objects from Arrow Functions

Wrap an object literal in parentheses when returning it implicitly from an arrow function.

Return Object Literals
const names = [
  "Alice",
  "Bob"
];

const users =
  names.map(
    (name, index) => ({
      id: index + 1,
      name
    })
  );

console.log(users);
Output
[
  { 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.

filter()
const numbers = [
  1,
  2,
  3,
  4,
  5,
  6
];

const evenNumbers =
  numbers.filter(number =>
    number % 2 === 0
  );

console.log(evenNumbers);
Output
[2, 4, 6]

Filtering Objects

Filter Active Users
const users = [
  {
    name: "Alice",
    active: true
  },
  {
    name: "Bob",
    active: false
  },
  {
    name: "Maya",
    active: true
  }
];

const activeUsers =
  users.filter(user =>
    user.active
  );

console.log(activeUsers);
Output
[
  { name: "Alice", active: true },
  { name: "Maya", active: true }
]

Removing Falsy Values with filter()

Passing Boolean removes all falsy values.

Remove Falsy Values
const values = [
  "JavaScript",
  "",
  null,
  "Python",
  undefined,
  0,
  "SQL"
];

const cleaned =
  values.filter(Boolean);

console.log(cleaned);
Output
["JavaScript", "Python", "SQL"]
Valid Falsy Values Are Also Removed

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.

find()
const users = [
  {
    id: 1,
    name: "Alice"
  },
  {
    id: 2,
    name: "Bob"
  }
];

const user =
  users.find(item =>
    item.id === 2
  );

console.log(user);
Output
{ id: 2, name: "Bob" }

findIndex()

The findIndex() method returns the index of the first matching element, or -1 when no match exists.

findIndex()
const users = [
  {
    id: 1,
    name: "Alice"
  },
  {
    id: 2,
    name: "Bob"
  }
];

const index =
  users.findIndex(user =>
    user.id === 2
  );

console.log(index);
Output
1

findLast() and findLastIndex()

These methods search from the end of the array.

Search from the End
const numbers = [
  3,
  8,
  4,
  10,
  6
];

console.log(
  numbers.findLast(
    number =>
      number > 5
  )
);

console.log(
  numbers.findLastIndex(
    number =>
      number > 5
  )
);
Output
6
4

some()

The some() method returns true when at least one element satisfies the callback.

some()
const scores = [
  42,
  58,
  91,
  67
];

const hasHighScore =
  scores.some(score =>
    score >= 90
  );

console.log(hasHighScore);
Output
true

every()

The every() method returns true only when every element satisfies the callback.

every()
const ages = [
  24,
  31,
  19,
  42
];

const allAdults =
  ages.every(age =>
    age >= 18
  );

console.log(allAdults);
Output
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.

Sum with reduce()
const prices = [
  10,
  20,
  30
];

const total =
  prices.reduce(
    (sum, price) =>
      sum + price,
    0
  );

console.log(total);
Output
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.

Safe reduce()
const values = [];

const total =
  values.reduce(
    (sum, value) =>
      sum + value,
    0
  );

console.log(total);
Output
0

Counting Values with reduce()

Frequency Counter
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);
Output
{
  red: 3,
  blue: 2,
  green: 1
}

Grouping Objects with reduce()

Group by Category
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);
Output
{
  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.

reduceRight()
const words = [
  "JavaScript",
  "is",
  "powerful"
];

const result =
  words.reduceRight(
    (sentence, word) =>
      sentence
        ? `${sentence} ${word}`
        : word,
    ""
  );

console.log(result);
Output
powerful is JavaScript

flat()

The flat() method creates a new array with nested arrays flattened to a specified depth.

flat()
const values = [
  1,
  [2, 3],
  [4, [5, 6]]
];

console.log(
  values.flat()
);

console.log(
  values.flat(2)
);
Output
[1, 2, 3, 4, [5, 6]]
[1, 2, 3, 4, 5, 6]

Flattening Every Level

flat(Infinity)
const deeplyNested = [
  1,
  [2, [3, [4]]]
];

const flatValues =
  deeplyNested.flat(
    Infinity
  );

console.log(flatValues);
Output
[1, 2, 3, 4]

flatMap()

The flatMap() method maps every element and then flattens the result by one level.

flatMap()
const sentences = [
  "Learn JavaScript",
  "Build projects"
];

const words =
  sentences.flatMap(
    sentence =>
      sentence.split(" ")
  );

console.log(words);
Output
["Learn", "JavaScript", "Build", "projects"]

Removing and Expanding with flatMap()

Returning an empty array removes an element. Returning multiple values expands one element into several.

Filter and Map Together
const numbers = [
  1,
  2,
  3,
  4
];

const result =
  numbers.flatMap(
    number =>
      number % 2 === 0
        ? [
            number,
            number * 10
          ]
        : []
  );

console.log(result);
Output
[2, 20, 4, 40]

Method Chaining

Non-mutating array methods can be chained to build readable data-processing pipelines.

Filter, Map, and Reduce
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);
Output
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

Avoid These Mistakes
  • Using forEach() when a new array is required.
  • Forgetting to return a value from a map() callback.
  • Using filter(Boolean) when 0 or false are valid values.
  • Forgetting that find() returns undefined when no match exists.
  • Forgetting that findIndex() returns -1 when 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.
Best Practice

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() and findIndex() return the first match.
  • findLast() and findLastIndex() 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.

includes()
const languages = [
  "JavaScript",
  "Python",
  "SQL"
];

console.log(
  languages.includes(
    "Python"
  )
);

console.log(
  languages.includes(
    "PHP"
  )
);
Output
true
false

Searching with indexOf()

The indexOf() method returns the first matching index, or -1 when the value is not found.

indexOf()
const colors = [
  "red",
  "green",
  "blue",
  "green"
];

console.log(
  colors.indexOf(
    "green"
  )
);

console.log(
  colors.indexOf(
    "yellow"
  )
);
Output
1
-1

Searching from the End with lastIndexOf()

lastIndexOf()
const colors = [
  "red",
  "green",
  "blue",
  "green"
];

console.log(
  colors.lastIndexOf(
    "green"
  )
);
Output
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.
NaN Difference

includes(NaN) can find NaN, while indexOf(NaN) returns -1.

Searching for NaN
const values = [
  10,
  NaN,
  20
];

console.log(
  values.includes(NaN)
);

console.log(
  values.indexOf(NaN)
);
Output
true
-1

Sorting Strings with sort()

The sort() method changes the original array and sorts values as strings by default.

Default String Sort
const languages = [
  "Python",
  "JavaScript",
  "CSS",
  "HTML"
];

languages.sort();

console.log(languages);
Output
["CSS", "HTML", "JavaScript", "Python"]

Default Numeric Sorting Problem

Without a comparison function, numbers are converted to strings before sorting.

Incorrect Numeric Sort
const numbers = [
  2,
  100,
  15,
  8
];

numbers.sort();

console.log(numbers);
Output
[100, 15, 2, 8]
Why?

The values are compared as text, so "100" comes before "15" and "2".

Sorting Numbers Correctly

Numeric Comparison
const numbers = [
  2,
  100,
  15,
  8
];

numbers.sort(
  (first, second) =>
    first - second
);

console.log(numbers);
Output
[2, 8, 15, 100]

Ascending and Descending Sort

Order Comparison Function
Ascending (a, b) => a - b
Descending (a, b) => b - a
Descending Sort
const numbers = [
  2,
  100,
  15,
  8
];

numbers.sort(
  (first, second) =>
    second - first
);

console.log(numbers);
Output
[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.

Sort Products by Price
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);
Output
[
  { name: "Mouse", price: 40 },
  { name: "Keyboard", price: 80 },
  { name: "Laptop", price: 1200 }
]

Sorting Text Properties

Use localeCompare() for readable text sorting.

Sort Users by Name
const users = [
  {
    name: "Maya"
  },
  {
    name: "Alice"
  },
  {
    name: "Bob"
  }
];

users.sort(
  (first, second) =>
    first.name.localeCompare(
      second.name
    )
);

console.log(users);
Output
[
  { name: "Alice" },
  { name: "Bob" },
  { name: "Maya" }
]

Non-Mutating Sorting with toSorted()

The toSorted() method returns a sorted copy and preserves the original array.

toSorted()
const numbers = [
  20,
  5,
  100,
  12
];

const sorted =
  numbers.toSorted(
    (first, second) =>
      first - second
  );

console.log(numbers);
console.log(sorted);
Output
[20, 5, 100, 12]
[5, 12, 20, 100]

Older Non-Mutating Sort Pattern

Spread syntax followed by sort() also preserves the original outer array.

Copy Before Sorting
const numbers = [
  20,
  5,
  100,
  12
];

const sorted = [
  ...numbers
].sort(
  (first, second) =>
    first - second
);

console.log(numbers);
console.log(sorted);
Output
[20, 5, 100, 12]
[5, 12, 20, 100]

Sorting by Multiple Properties

Combine comparisons to create primary and secondary sort rules.

Multiple Sort Criteria
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);
Output
[
  { name: "Alice", age: 25 },
  { name: "Bob", age: 25 },
  { name: "Maya", age: 30 }
]

Array Destructuring

Destructuring extracts array elements into variables according to their position.

Basic Destructuring
const languages = [
  "JavaScript",
  "Python",
  "SQL"
];

const [
  first,
  second,
  third
] = languages;

console.log(first);
console.log(second);
console.log(third);
Output
JavaScript
Python
SQL

Skipping Elements

Skip Array Positions
const values = [
  "A",
  "B",
  "C"
];

const [
  first,
  ,
  third
] = values;

console.log(first);
console.log(third);
Output
A
C

Default Values in Destructuring

Destructuring Defaults
const values = [
  "JavaScript"
];

const [
  language,
  level = "Beginner"
] = values;

console.log(language);
console.log(level);
Output
JavaScript
Beginner

Rest Elements in Destructuring

Rest Pattern
const values = [
  10,
  20,
  30,
  40
];

const [
  first,
  ...remaining
] = values;

console.log(first);
console.log(remaining);
Output
10
[20, 30, 40]

Swapping Variables

Swap with Destructuring
let first = "A";
let second = "B";

[
  first,
  second
] = [
  second,
  first
];

console.log(first);
console.log(second);
Output
B
A

Multidimensional Arrays

Nested arrays can represent grids, matrices, tables, game boards, and grouped data.

Matrix Access
const matrix = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
];

console.log(
  matrix[1][2]
);
Output
6

Looping Through Nested Arrays

Nested for...of Loops
const matrix = [
  [1, 2],
  [3, 4],
  [5, 6]
];

for (const row of matrix) {
  for (const value of row) {
    console.log(value);
  }
}
Output
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.

Deduplicate with Set
const values = [
  "JavaScript",
  "Python",
  "JavaScript",
  "SQL",
  "Python"
];

const uniqueValues = [
  ...new Set(values)
];

console.log(uniqueValues);
Output
["JavaScript", "Python", "SQL"]

Removing Duplicate Objects by Property

Objects are compared by reference, so deduplicating records requires a specific key or custom strategy.

Unique Objects by ID
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);
Output
[
  { id: 1, name: "Alice" },
  { id: 2, name: "Bob" }
]

Creating an Index by ID

A Map provides fast lookup when records have unique keys.

Map Index
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)
);
Output
{ id: 2, name: "Bob" }

Finding Minimum and Maximum Values

Math.min() and Math.max()
const numbers = [
  12,
  5,
  87,
  24
];

console.log(
  Math.min(...numbers)
);

console.log(
  Math.max(...numbers)
);
Output
5
87
Large Arrays

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

Average with reduce()
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);
Output
85

Randomizing an Array

A Fisher–Yates shuffle provides an unbiased in-place random ordering.

Fisher–Yates Shuffle
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])
);
Avoid sort(() => Math.random() - 0.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.

Reference Equality
const first = [
  1,
  2,
  3
];

const second = [
  1,
  2,
  3
];

const shared = first;

console.log(
  first === second
);

console.log(
  first === shared
);
Output
false
true

Comparing Primitive Arrays by Value

Simple Value Comparison
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]
  )
);
Output
true
false
Comparison Scope

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

Recommended Practices
  • Use array literals instead of the constructor for ordinary arrays.
  • Use const when the array variable will not be reassigned.
  • Use Array.isArray() for reliable array checks.
  • Prefer map(), filter(), and find() when they clearly express the task.
  • Use toSorted(), toReversed(), toSpliced(), and with() 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 Map or Set when 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
Mutation Reminder

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.
  • Set removes duplicate primitive values.
  • Map supports 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.

Object Literal
const user = {
  name: "Alice",
  age: 30,
  active: true
};

console.log(user);
Output
{
  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.

Access with Dot Notation
const user = {
  name: "Alice",
  age: 30
};

console.log(user.name);
console.log(user.age);
Output
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.

Access with Bracket Notation
const user = {
  name: "Alice",
  "account status": "active"
};

console.log(
  user["name"]
);

console.log(
  user["account status"]
);
Output
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.

Dynamic Key
const user = {
  name: "Alice",
  age: 30
};

const propertyName =
  "name";

console.log(
  user[propertyName]
);
Output
Alice
Common Dot-Notation Mistake
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.

Add Object Properties
const user = {
  name: "Alice"
};

user.age = 30;

user["account status"] =
  "active";

console.log(user);
Output
{
  name: "Alice",
  age: 30,
  "account status": "active"
}

Updating Properties

Assigning a value to an existing property replaces its current value.

Update Object Properties
const user = {
  name: "Alice",
  age: 30
};

user.name = "Maya";
user.age = 31;

console.log(user);
Output
{
  name: "Maya",
  age: 31
}

Deleting Properties

The delete operator removes a property from an object and normally returns true.

delete Operator
const user = {
  name: "Alice",
  age: 30,
  password: "secret"
};

const deleted =
  delete user.password;

console.log(deleted);
console.log(user);
Output
true
{
  name: "Alice",
  age: 30
}

Missing Properties

Reading a property that does not exist returns undefined.

Missing Property
const user = {
  name: "Alice"
};

console.log(
  user.email
);
Output
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
Object.hasOwn()
const user = {
  name: "Alice",
  email: undefined
};

console.log(
  Object.hasOwn(
    user,
    "name"
  )
);

console.log(
  Object.hasOwn(
    user,
    "email"
  )
);

console.log(
  Object.hasOwn(
    user,
    "age"
  )
);
Output
true
true
false
Why Object.hasOwn() Is Better

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.

in Operator
const user = {
  name: "Alice"
};

console.log(
  "name" in user
);

console.log(
  "toString" in user
);

console.log(
  "email" in user
);
Output
true
true
false

Computed Property Names

Square brackets inside an object literal allow an expression to determine the property key.

Computed Property
const fieldName =
  "email";

const user = {
  name: "Alice",
  [fieldName]:
    "alice@example.com"
};

console.log(user);
Output
{
  name: "Alice",
  email: "alice@example.com"
}

Property Shorthand

When a variable name matches the desired property key, include the variable without repeating its name.

Property Shorthand
const name = "Alice";
const age = 30;
const active = true;

const user = {
  name,
  age,
  active
};

console.log(user);
Output
{
  name: "Alice",
  age: 30,
  active: true
}

Methods Inside Objects

A function stored as an object property is called a method.

Object Method
const user = {
  name: "Alice",

  greet: function () {
    return "Hello!";
  }
};

console.log(
  user.greet()
);
Output
Hello!

Method Shorthand

Modern object literals support a shorter syntax for declaring methods.

Method Shorthand
const user = {
  name: "Alice",

  greet() {
    return `Hello ${this.name}`;
  }
};

console.log(
  user.greet()
);
Output
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.

this in an Object Method
const product = {
  name: "Keyboard",
  price: 80,
  quantity: 2,

  getTotal() {
    return (
      this.price *
      this.quantity
    );
  }
};

console.log(
  product.getTotal()
);
Output
160
Avoid Arrow Functions as this-Based Methods
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.

Nested Object
const user = {
  name: "Alice",

  address: {
    city: "London",
    country: "UK"
  }
};

console.log(
  user.address.city
);

console.log(
  user["address"]["country"]
);
Output
London
UK

Objects Containing Arrays

Array Property
const course = {
  title:
    "JavaScript Fundamentals",

  topics: [
    "Variables",
    "Arrays",
    "Objects"
  ]
};

console.log(
  course.topics[1]
);
Output
Arrays

Optional Chaining

The optional chaining operator ?. safely accesses a property or method when an intermediate value may be null or undefined.

Optional Property Access
const user = {
  name: "Alice"
};

console.log(
  user.address?.city
);

console.log(
  user.profile?.settings?.theme
);
Output
undefined
undefined

Optional Method Calls

Optional Method
const user = {
  name: "Alice"
};

const result =
  user.greet?.();

console.log(result);
Output
undefined
Optional Chaining Is Not Validation

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.

Safe Fallback Value
const user = {
  name: "Alice",
  settings: {
    theme: null
  }
};

const theme =
  user.settings?.theme ??
  "system";

console.log(theme);
Output
system

Why const Objects Can Change

const prevents reassignment of the variable. It does not make the object or its properties immutable.

Mutable const Object
const user = {
  name: "Alice"
};

user.name = "Maya";
user.active = true;

console.log(user);
Output
{
  name: "Maya",
  active: true
}
Reassignment Is Not Allowed
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.

Shared Object Reference
const firstUser = {
  name: "Alice"
};

const secondUser =
  firstUser;

secondUser.name = "Maya";

console.log(
  firstUser.name
);

console.log(
  secondUser.name
);
Output
Maya
Maya

Object Equality

Strict equality compares object references, not their property contents.

Reference Equality
const first = {
  name: "Alice"
};

const second = {
  name: "Alice"
};

const shared = first;

console.log(
  first === second
);

console.log(
  first === shared
);
Output
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.
Best Practice

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.
  • const prevents 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.

Object.keys()
const user = {
  name: "Alice",
  age: 30,
  active: true
};

const keys =
  Object.keys(user);

console.log(keys);
Output
["name", "age", "active"]

Counting Object Properties

Objects do not have a normal length property. Use the length of the array returned by Object.keys().

Count Properties
const settings = {
  theme: "dark",
  language: "en",
  notifications: true
};

const propertyCount =
  Object.keys(settings).length;

console.log(propertyCount);
Output
3

Iterating Over Object Keys

Because Object.keys() returns an array, it can be used with for...of, forEach(), and other array methods.

Loop Through Keys
const user = {
  name: "Alice",
  age: 30,
  active: true
};

for (
  const key of
  Object.keys(user)
) {
  console.log(
    key,
    user[key]
  );
}
Output
name Alice
age 30
active true

Filtering Object Keys

Filter 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);
Output
["price", "stock"]

Object.values()

The Object.values() method returns an array containing the object's own enumerable string-keyed property values.

Object.values()
const user = {
  name: "Alice",
  age: 30,
  active: true
};

const values =
  Object.values(user);

console.log(values);
Output
["Alice", 30, true]

Calculating a Total with Object.values()

Sum Object Values
const monthlySales = {
  january: 1200,
  february: 1500,
  march: 1800
};

const total =
  Object.values(
    monthlySales
  ).reduce(
    (sum, value) =>
      sum + value,
    0
  );

console.log(total);
Output
4500

Testing Object Values

Array methods such as some() and every() can test the values returned by Object.values().

Test Property 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
);
Output
true
false

Object.entries()

The Object.entries() method returns an array of [key, value] pairs.

Object.entries()
const user = {
  name: "Alice",
  age: 30,
  active: true
};

const entries =
  Object.entries(user);

console.log(entries);
Output
[
  ["name", "Alice"],
  ["age", 30],
  ["active", true]
]

Iterating with Object.entries()

Destructure each entry into a key and value while iterating.

Entry Destructuring
const product = {
  name: "Keyboard",
  price: 80,
  stock: 12
};

for (
  const [key, value] of
  Object.entries(product)
) {
  console.log(
    `${key}: ${value}`
  );
}
Output
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().

Filter an Object
const user = {
  name: "Alice",
  age: 30,
  password: "secret",
  active: true
};

const publicUser =
  Object.fromEntries(
    Object.entries(user)
      .filter(
        ([key]) =>
          key !== "password"
      )
  );

console.log(publicUser);
Output
{
  name: "Alice",
  age: 30,
  active: true
}

Transforming Object Values

Use map() on the entries to transform the values while preserving the keys.

Transform Object Values
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
);
Output
{
  laptop: 1080,
  keyboard: 72,
  mouse: 36
}

Transforming Object Keys

Rename Object Keys
const settings = {
  darkMode: true,
  emailAlerts: false
};

const uppercaseKeys =
  Object.fromEntries(
    Object.entries(settings)
      .map(
        ([key, value]) => [
          key.toUpperCase(),
          value
        ]
      )
  );

console.log(
  uppercaseKeys
);
Output
{
  DARKMODE: true,
  EMAILALERTS: false
}

Object.fromEntries()

The Object.fromEntries() method converts an iterable of key-value pairs into an object.

Object.fromEntries()
const entries = [
  ["name", "Alice"],
  ["age", 30],
  ["active", true]
];

const user =
  Object.fromEntries(
    entries
  );

console.log(user);
Output
{
  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().

Map to Object
const settingsMap =
  new Map([
    ["theme", "dark"],
    ["language", "en"],
    ["notifications", true]
  ]);

const settings =
  Object.fromEntries(
    settingsMap
  );

console.log(settings);
Output
{
  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.

URLSearchParams to Object
const parameters =
  new URLSearchParams(
    "page=2&sort=price&order=asc"
  );

const query =
  Object.fromEntries(
    parameters
  );

console.log(query);
Output
{
  page: "2",
  sort: "price",
  order: "asc"
}
Duplicate Keys

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.

Check Own Properties
const user = {
  name: "Alice",
  email: undefined
};

console.log(
  Object.hasOwn(
    user,
    "name"
  )
);

console.log(
  Object.hasOwn(
    user,
    "email"
  )
);

console.log(
  Object.hasOwn(
    user,
    "toString"
  )
);
Output
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
Own vs Inherited Property
const user = {
  name: "Alice"
};

console.log(
  Object.hasOwn(
    user,
    "toString"
  )
);

console.log(
  "toString" in user
);
Output
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.
Safe Ownership Check
const data =
  Object.create(null);

data.name = "Alice";

console.log(
  Object.hasOwn(
    data,
    "name"
  )
);
Output
true

Enumerable Own Properties

Object.keys(), Object.values(), and Object.entries() include only own enumerable string-keyed properties.

Non-Enumerable Property
const user = {
  name: "Alice"
};

Object.defineProperty(
  user,
  "id",
  {
    value: 123,
    enumerable: false
  }
);

console.log(
  Object.keys(user)
);

console.log(user.id);
Output
["name"]
123

Symbol Properties Are Excluded

The standard keys, values, and entries methods do not include symbol-keyed properties.

Symbol Key
const identifier =
  Symbol("id");

const user = {
  name: "Alice",
  [identifier]: 123
};

console.log(
  Object.keys(user)
);

console.log(
  user[identifier]
);
Output
["name"]
123

Object.getOwnPropertyNames()

This method returns own string-keyed property names, including non-enumerable properties.

All Own String Keys
const user = {
  name: "Alice"
};

Object.defineProperty(
  user,
  "id",
  {
    value: 123,
    enumerable: false
  }
);

console.log(
  Object.getOwnPropertyNames(
    user
  )
);
Output
["name", "id"]

Object.getOwnPropertySymbols()

Use this method to retrieve an object's own symbol-keyed properties.

Own Symbol Keys
const identifier =
  Symbol("id");

const user = {
  name: "Alice",
  [identifier]: 123
};

const symbols =
  Object.getOwnPropertySymbols(
    user
  );

console.log(symbols);
console.log(
  user[symbols[0]]
);
Output
[Symbol(id)]
123

Reflect.ownKeys()

The Reflect.ownKeys() method returns all own property keys, including strings, symbols, enumerable properties, and non-enumerable properties.

All Own Keys
const identifier =
  Symbol("id");

const user = {
  name: "Alice",
  [identifier]: 123
};

Object.defineProperty(
  user,
  "secret",
  {
    value: true,
    enumerable: false
  }
);

console.log(
  Reflect.ownKeys(user)
);
Output
[
  "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

Avoid These Mistakes
  • Expecting objects to have a built-in length property.
  • 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.
Best Practice

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 in operator 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.

Object Spread Copy
const original = {
  name: "Alice",
  age: 30
};

const copy = {
  ...original
};

copy.name = "Maya";

console.log(original);
console.log(copy);
Output
{
  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.

Object.assign()
const original = {
  name: "Alice",
  age: 30
};

const copy =
  Object.assign(
    {},
    original
  );

copy.age = 31;

console.log(original);
console.log(copy);
Output
{
  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.

Immutable Object Update
const user = {
  id: 1,
  name: "Alice",
  active: false
};

const updatedUser = {
  ...user,
  active: true
};

console.log(user);
console.log(updatedUser);
Output
{
  id: 1,
  name: "Alice",
  active: false
}

{
  id: 1,
  name: "Alice",
  active: true
}
Property Order Matters
const wrongOrder = {
  active: true,
  ...user
};

If user.active is false, the spread operation overwrites the earlier true value.

Adding Properties Immutably

Add a New Property
const user = {
  name: "Alice",
  age: 30
};

const updatedUser = {
  ...user,
  role: "admin"
};

console.log(updatedUser);
Output
{
  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.

Remove with Rest Syntax
const user = {
  name: "Alice",
  age: 30,
  password: "secret"
};

const {
  password,
  ...publicUser
} = user;

console.log(publicUser);
console.log(password);
Output
{
  name: "Alice",
  age: 30
}

secret

Removing a Dynamic Property

Computed property destructuring can remove a key stored in a variable.

Remove Dynamic Key
const user = {
  name: "Alice",
  age: 30,
  password: "secret"
};

const keyToRemove =
  "password";

const {
  [keyToRemove]: removed,
  ...remaining
} = user;

console.log(remaining);
console.log(removed);
Output
{
  name: "Alice",
  age: 30
}

secret

Merging Objects with Spread Syntax

Spread multiple objects into a new object. When keys conflict, the last value wins.

Merge Objects
const defaults = {
  theme: "light",
  language: "en",
  notifications: true
};

const preferences = {
  theme: "dark",
  notifications: false
};

const settings = {
  ...defaults,
  ...preferences
};

console.log(settings);
Output
{
  theme: "dark",
  language: "en",
  notifications: false
}

Merging with Object.assign()

Object.assign() Merge
const defaults = {
  theme: "light",
  language: "en"
};

const preferences = {
  theme: "dark"
};

const settings =
  Object.assign(
    {},
    defaults,
    preferences
  );

console.log(settings);
Output
{
  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.

Mutating Target Object
const settings = {
  theme: "light"
};

const result =
  Object.assign(
    settings,
    {
      theme: "dark",
      language: "en"
    }
  );

console.log(settings);
console.log(
  settings === result
);
Output
{
  theme: "dark",
  language: "en"
}

true
Preserve the Source

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.

Shared Nested Object
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
);
Output
Manchester
Manchester
Shallow Copy Limitation

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.

Nested Spread Copy
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
);
Output
London
Manchester

Updating a Nested Property Immutably

Nested Immutable Update
const user = {
  name: "Alice",

  settings: {
    theme: "light",
    notifications: true
  }
};

const updatedUser = {
  ...user,

  settings: {
    ...user.settings,
    theme: "dark"
  }
};

console.log(user);
console.log(updatedUser);
Output
{
  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.

Nested Array Update
const course = {
  title: "JavaScript",

  topics: [
    "Variables",
    "Arrays"
  ]
};

const updatedCourse = {
  ...course,

  topics: [
    ...course.topics,
    "Objects"
  ]
};

console.log(course);
console.log(updatedCourse);
Output
{
  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.

Update Array Record
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);
Output
[
  {
    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.

Top-Level Merge Limitation
const defaults = {
  settings: {
    theme: "light",
    language: "en"
  }
};

const preferences = {
  settings: {
    theme: "dark"
  }
};

const merged = {
  ...defaults,
  ...preferences
};

console.log(merged);
Output
{
  settings: {
    theme: "dark"
  }
}
Nested Data Is Replaced

The language property disappears because the entire settings object from preferences replaces the earlier one.

Manually Merging a Nested Level

Nested Merge
const defaults = {
  settings: {
    theme: "light",
    language: "en"
  }
};

const preferences = {
  settings: {
    theme: "dark"
  }
};

const merged = {
  ...defaults,
  ...preferences,

  settings: {
    ...defaults.settings,
    ...preferences.settings
  }
};

console.log(merged);
Output
{
  settings: {
    theme: "dark",
    language: "en"
  }
}

Conditional Object Properties

Spread an object conditionally to include properties only when a condition is true.

Conditional Spread
const isAdmin = true;
const includeEmail = false;

const user = {
  name: "Alice",

  ...(isAdmin && {
    role: "admin"
  }),

  ...(includeEmail && {
    email:
      "alice@example.com"
  })
};

console.log(user);
Output
{
  name: "Alice",
  role: "admin"
}

Dynamic Property Updates

Computed property names make immutable updates possible when the property key is stored in a variable.

Update a Dynamic Key
const settings = {
  theme: "light",
  language: "en"
};

const key = "theme";
const value = "dark";

const updatedSettings = {
  ...settings,
  [key]: value
};

console.log(
  updatedSettings
);
Output
{
  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.

Getter Becomes a Value
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"
  )
);
Conceptual Result
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.

Descriptor-Preserving Copy
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
);
Output
Alice Smith
function

Spread and Symbol Properties

Object spread copies own enumerable symbol-keyed properties as well as own enumerable string-keyed properties.

Copy an Enumerable Symbol
const identifier =
  Symbol("id");

const source = {
  name: "Alice",
  [identifier]: 123
};

const copy = {
  ...source
};

console.log(
  copy[identifier]
);

console.log(
  Reflect.ownKeys(copy)
);
Output
123
["name", Symbol(id)]

Non-Enumerable Properties Are Not Copied

Non-Enumerable Property
const source = {
  name: "Alice"
};

Object.defineProperty(
  source,
  "secret",
  {
    value: true,
    enumerable: false
  }
);

const copy = {
  ...source
};

console.log(copy);
console.log(
  copy.secret
);
Output
{
  name: "Alice"
}

undefined

Common Copying and Merging Mistakes

Avoid These 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.
Best Practice

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.

Basic Destructuring
const user = {
  name: "Alice",
  age: 30,
  active: true
};

const {
  name,
  age,
  active
} = user;

console.log(name);
console.log(age);
console.log(active);
Output
Alice
30
true

Destructuring Is Based on Property Names

Unlike array destructuring, object destructuring does not depend on property order.

Order Does Not Matter
const product = {
  name: "Keyboard",
  price: 80,
  stock: 12
};

const {
  stock,
  name
} = product;

console.log(name);
console.log(stock);
Output
Keyboard
12

Renaming Destructured Variables

Use a colon to assign a property value to a variable with a different name.

Property Alias
const user = {
  name: "Alice",
  age: 30
};

const {
  name: userName,
  age: userAge
} = user;

console.log(userName);
console.log(userAge);
Output
Alice
30
Read the Syntax Correctly

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.

Destructuring Defaults
const user = {
  name: "Alice"
};

const {
  name,
  role = "member",
  active = true
} = user;

console.log(name);
console.log(role);
console.log(active);
Output
Alice
member
true

Defaults Do Not Replace null

undefined vs null
const settings = {
  theme: null,
  language: undefined
};

const {
  theme = "light",
  language = "en"
} = settings;

console.log(theme);
console.log(language);
Output
null
en
Use ?? for nullish Fallbacks

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

Alias with Default
const user = {
  name: "Alice"
};

const {
  name: displayName =
    "Anonymous",

  role: userRole =
    "member"
} = user;

console.log(displayName);
console.log(userRole);
Output
Alice
member

Nested Object Destructuring

Nested patterns can extract values from objects inside other objects.

Nested Destructuring
const user = {
  name: "Alice",

  address: {
    city: "London",
    country: "UK"
  }
};

const {
  name,

  address: {
    city,
    country
  }
} = user;

console.log(name);
console.log(city);
console.log(country);
Output
Alice
London
UK
The Parent Variable Is Not Created

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.

Nested Object Default
const user = {
  name: "Alice"
};

const {
  address: {
    city = "Unknown"
  } = {}
} = user;

console.log(city);
Output
Unknown
null Still Causes a Problem

The default {} is used only when address is undefined. If it is explicitly null, nested destructuring still throws a TypeError.

Destructuring Arrays Inside Objects

Nested Array Pattern
const course = {
  title: "JavaScript",

  topics: [
    "Variables",
    "Arrays",
    "Objects"
  ]
};

const {
  title,

  topics: [
    firstTopic,
    secondTopic
  ]
} = course;

console.log(title);
console.log(firstTopic);
console.log(secondTopic);
Output
JavaScript
Variables
Arrays

Destructuring Objects Inside Arrays

Array and Object Pattern
const users = [
  {
    id: 1,
    name: "Alice"
  },
  {
    id: 2,
    name: "Bob"
  }
];

const [
  {
    name: firstName
  },
  {
    name: secondName
  }
] = users;

console.log(firstName);
console.log(secondName);
Output
Alice
Bob

Rest Properties

Rest syntax collects the remaining own enumerable properties into a new object.

Object Rest Syntax
const user = {
  id: 1,
  name: "Alice",
  age: 30,
  active: true
};

const {
  id,
  ...details
} = user;

console.log(id);
console.log(details);
Output
1

{
  name: "Alice",
  age: 30,
  active: true
}

Excluding Several Properties

Create a Public Object
const user = {
  id: 1,
  name: "Alice",
  email:
    "alice@example.com",
  password: "secret",
  token: "abc123"
};

const {
  password,
  token,
  ...publicUser
} = user;

console.log(publicUser);
Output
{
  id: 1,
  name: "Alice",
  email: "alice@example.com"
}
Rest Creates a Shallow Copy

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.

Dynamic Property Extraction
const settings = {
  theme: "dark",
  language: "en"
};

const key = "theme";

const {
  [key]: selectedValue
} = settings;

console.log(
  selectedValue
);
Output
dark

Destructuring an Existing Variable

Wrap an assignment pattern in parentheses when assigning to variables that already exist.

Destructuring Assignment
let name;
let age;

const user = {
  name: "Alice",
  age: 30
};

({
  name,
  age
} = user);

console.log(name);
console.log(age);
Output
Alice
30
Why the Parentheses?

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.

Parameter Destructuring
function displayUser({
  name,
  age,
  active
}) {
  console.log(
    `${name}, ${age}, ${active}`
  );
}

displayUser({
  name: "Alice",
  age: 30,
  active: true
});
Output
Alice, 30, true

Parameter Defaults

Default Parameter Properties
function createButton({
  text = "Submit",
  type = "button",
  disabled = false
} = {}) {
  return {
    text,
    type,
    disabled
  };
}

console.log(
  createButton({
    text: "Save"
  })
);

console.log(
  createButton()
);
Output
{
  text: "Save",
  type: "button",
  disabled: false
}

{
  text: "Submit",
  type: "button",
  disabled: false
}
Why = {} Matters

The outer default allows the function to be called without an argument. Without it, destructuring undefined would throw a TypeError.

Renaming Destructured Parameters

Parameter Alias
function printProduct({
  name: productName,
  price: productPrice
}) {
  console.log(
    `${productName}: $${productPrice}`
  );
}

printProduct({
  name: "Keyboard",
  price: 80
});
Output
Keyboard: $80

Nested Parameter Destructuring

Nested Function Parameter
function printLocation({
  name,

  address: {
    city = "Unknown",
    country = "Unknown"
  } = {}
}) {
  console.log(
    `${name}: ${city}, ${country}`
  );
}

printLocation({
  name: "Alice",

  address: {
    city: "London",
    country: "UK"
  }
});
Output
Alice: London, UK

Returning Multiple Named Values

Functions can return an object so callers can extract only the properties they need.

Destructure a Return Value
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);
Output
14
40

Iterating with Object.keys()

Use Object.keys() when you primarily need the property names.

Key Iteration
const user = {
  name: "Alice",
  age: 30,
  active: true
};

for (
  const key of
  Object.keys(user)
) {
  console.log(
    `${key}: ${user[key]}`
  );
}
Output
name: Alice
age: 30
active: true

Iterating with Object.values()

Use Object.values() when property names are irrelevant.

Value Iteration
const scores = {
  testOne: 82,
  testTwo: 91,
  testThree: 87
};

let total = 0;

for (
  const score of
  Object.values(scores)
) {
  total += score;
}

console.log(total);
Output
260

Iterating with Object.entries()

Use entries when both the key and value are needed.

Entry Iteration
const settings = {
  theme: "dark",
  language: "en",
  notifications: true
};

for (
  const [key, value] of
  Object.entries(settings)
) {
  console.log(
    key,
    value
  );
}
Output
theme dark
language en
notifications true

The for...in Loop

A for...in loop iterates over enumerable string-keyed properties, including inherited ones.

for...in
const user = {
  name: "Alice",
  age: 30
};

for (const key in user) {
  console.log(
    key,
    user[key]
  );
}
Output
name Alice
age 30

Inherited Properties with for...in

Inherited Enumerable Property
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]
  );
}
Output
name Alice
age 30
role member

Safe for...in Iteration

Filter with Object.hasOwn() when only direct properties should be processed.

Own Properties Only
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]
    );
  }
}
Output
name Alice
age 30
Preferred Default

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.

Transform Numeric Values
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
);
Output
{
  laptop: 1320,
  keyboard: 88,
  mouse: 44
}

Filtering an Object by Value

Keep Active Features
const features = {
  search: true,
  export: false,
  analytics: true,
  comments: false
};

const enabledFeatures =
  Object.fromEntries(
    Object.entries(features)
      .filter(
        ([, enabled]) =>
          enabled
      )
  );

console.log(
  enabledFeatures
);
Output
{
  search: true,
  analytics: true
}

Sorting Object Entries

Although objects are not primarily sorted collections, entries can be sorted before processing or reconstruction.

Sort Entries by Value
const scores = {
  Alice: 82,
  Bob: 95,
  Maya: 88
};

const sortedEntries =
  Object.entries(scores)
    .toSorted(
      (
        [, firstScore],
        [, secondScore]
      ) =>
        secondScore -
        firstScore
    );

console.log(
  sortedEntries
);
Output
[
  ["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.

Index Records by ID
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]
);
Output
{
  id: 2,
  name: "Bob"
}
Consider Map for Keyed Collections

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

Group Products by Category
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);
Output
{
  Tech: [
    {
      name: "Laptop",
      category: "Tech"
    },
    {
      name: "Mouse",
      category: "Tech"
    }
  ],

  Furniture: [
    {
      name: "Chair",
      category: "Furniture"
    }
  ]
}

Counting Object Values

Count Status 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);
Output
{
  complete: 2,
  pending: 1,
  failed: 1
}

Destructuring and Iteration Mistakes

Avoid These 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...in without 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)
Best Practice

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(), and entries() support array-style processing.
  • for...in also 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.

APIPurposeImportant 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.
Descriptors and Cloning
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);
Remember: Object spread creates a shallow copy. Use 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 Declaration
function greet() {
  console.log("Hello!");
}

greet();
Output
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.

Definition and Invocation
function showMessage() {
  console.log(
    "Function executed"
  );
}

console.log(
  "Before the call"
);

showMessage();

console.log(
  "After the call"
);
Output
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.

Reusable Function
function showWelcome() {
  console.log(
    "Welcome to CheatSheetSilo"
  );
}

showWelcome();
showWelcome();
showWelcome();
Output
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.
Naming Convention

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.

Hoisted Function Declaration
greet();

function greet() {
  console.log(
    "Hello from a declaration"
  );
}
Output
Hello from a declaration
Readability First

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 Parameter
function greet(name) {
  console.log(
    `Hello, ${name}!`
  );
}

greet("Alice");
greet("Bob");
Output
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.

Multiple Parameters
function introduce(
  name,
  role,
  experience
) {
  console.log(
    `${name} is a ${role} ` +
    `with ${experience} years ` +
    "of experience."
  );
}

introduce(
  "Alice",
  "developer",
  5
);
Output
Alice is a developer with 5 years of experience.

Argument Order Matters

Positional Arguments
function subtract(
  first,
  second
) {
  return first - second;
}

console.log(
  subtract(10, 4)
);

console.log(
  subtract(4, 10)
);
Output
6
-6

Missing Arguments

When an argument is omitted, its corresponding parameter receives undefined.

Omitted Argument
function showUser(
  name,
  role
) {
  console.log(name);
  console.log(role);
}

showUser("Alice");
Output
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.

Extra Arguments
function greet(name) {
  console.log(
    `Hello, ${name}!`
  );
}

greet(
  "Alice",
  30,
  true
);
Output
Hello, Alice!

The return Statement

The return statement ends the current function call and sends a value back to the code that called it.

Return a Value
function add(
  first,
  second
) {
  return first + second;
}

const result =
  add(10, 5);

console.log(result);
Output
15

Returned Values Can Be Reused

Reuse a Function Result
function calculatePrice(
  price,
  quantity
) {
  return price * quantity;
}

const subtotal =
  calculatePrice(
    25,
    4
  );

const totalWithTax =
  subtotal * 1.2;

console.log(subtotal);
console.log(
  totalWithTax
);
Output
100
120

return Stops Function Execution

Statements after an executed return are not reached.

Early Return
function getAccessMessage(
  isLoggedIn
) {
  if (!isLoggedIn) {
    return "Please log in.";
  }

  return "Welcome back.";
}

console.log(
  getAccessMessage(false)
);

console.log(
  getAccessMessage(true)
);
Output
Please log in.
Welcome back.

Functions Without an Explicit Return

A function that reaches the end without executing a return statement returns undefined.

Implicit undefined
function logMessage() {
  console.log(
    "Message logged"
  );
}

const result =
  logMessage();

console.log(result);
Output
Message logged
undefined

Returning Multiple Values

A function returns one value, but that value can be an object or array containing several related results.

Return an Object
function calculate(
  first,
  second
) {
  return {
    sum: first + second,
    difference:
      first - second,
    product:
      first * second
  };
}

const result =
  calculate(10, 4);

console.log(result);
Output
{
  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.

Anonymous Function Expression
const greet =
  function () {
    console.log(
      "Hello from an expression"
    );
  };

greet();
Output
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.

Store and Call a Function
const multiply =
  function (
    first,
    second
  ) {
    return first * second;
  };

console.log(
  multiply(6, 7)
);
Output
42

Function Expression Hoisting

A function expression assigned to const or let cannot be called before the variable initialization.

Temporal Dead Zone
greet();

const greet =
  function () {
    console.log("Hello");
  };
Result

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.

Named Function Expression
const calculateFactorial =
  function factorial(
    number
  ) {
    if (number <= 1) {
      return 1;
    }

    return (
      number *
      factorial(
        number - 1
      )
    );
  };

console.log(
  calculateFactorial(5)
);
Output
120

Internal Function Name Scope

The internal name of a named function expression is normally available only inside that function.

Internal Name
const run =
  function internalName() {
    console.log(
      typeof internalName
    );
  };

run();

console.log(
  typeof internalName
);
Output
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.

Anonymous Callback
const numbers = [
  1,
  2,
  3
];

const doubled =
  numbers.map(
    function (number) {
      return number * 2;
    }
  );

console.log(doubled);
Output
[2, 4, 6]
Debugging Consideration

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.

Basic Arrow Function
const greet = () => {
  console.log(
    "Hello from an arrow function"
  );
};

greet();
Output
Hello from an arrow function

Arrow Function with One Parameter

Parentheses around one simple parameter are optional.

Single Parameter
const square =
  number => {
    return number * number;
  };

console.log(
  square(5)
);
Output
25

Arrow Function with Multiple Parameters

Parentheses are required for zero parameters, multiple parameters, default parameters, rest parameters, or destructured parameters.

Multiple Parameters
const add =
  (first, second) => {
    return first + second;
  };

console.log(
  add(10, 5)
);
Output
15

Implicit Return

When an arrow function contains one expression without braces, the expression's value is returned automatically.

Concise Arrow Function
const multiply =
  (first, second) =>
    first * second;

console.log(
  multiply(6, 7)
);
Output
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.

Implicit Object Return
const createUser =
  (name, age) => ({
    name,
    age,
    active: true
  });

console.log(
  createUser(
    "Alice",
    30
  )
);
Output
{
  name: "Alice",
  age: 30,
  active: true
}
Common Arrow Function Mistake
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

Arrow Callback
const prices = [
  10,
  20,
  30
];

const pricesWithTax =
  prices.map(
    price =>
      price * 1.2
  );

console.log(
  pricesWithTax
);
Output
[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
Do Not Use Arrow Functions for this-Based Object Methods
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.
Choosing a Function Style

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 function keyword and are hoisted.
  • Parameters define expected input, while arguments provide actual values.
  • The return statement 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 this or arguments.
  • 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.

Basic Default Parameter
function greet(
  name = "Guest"
) {
  return `Hello, ${name}!`;
}

console.log(
  greet("Alice")
);

console.log(
  greet()
);
Output
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 ""
Default Parameter Behavior
function showValue(
  value = "default"
) {
  console.log(value);
}

showValue();
showValue(undefined);
showValue(null);
showValue(false);
showValue(0);
showValue("");
Output
default
default
null
false
0

Default Parameters Do Not Replace null

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

Several Defaults
function createUser(
  name = "Anonymous",
  role = "member",
  active = true
) {
  return {
    name,
    role,
    active
  };
}

console.log(
  createUser()
);

console.log(
  createUser(
    "Alice",
    "admin",
    false
  )
);
Output
{
  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.

Skip a Positional Argument
function createMessage(
  text = "Hello",
  type = "info"
) {
  return `[${type}] ${text}`;
}

console.log(
  createMessage(
    undefined,
    "warning"
  )
);
Output
[warning] Hello
Consider an Options Object

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.

Expression as a Default
function createRecord(
  createdAt = new Date()
) {
  return {
    createdAt
  };
}

const record =
  createRecord();

console.log(
  record.createdAt
    instanceof Date
);
Output
true

Calling a Function in a Default Parameter

Calculated Default
function createIdentifier() {
  return Math.floor(
    Math.random() * 1000
  );
}

function createUser(
  name,
  id = createIdentifier()
) {
  return {
    id,
    name
  };
}

console.log(
  createUser("Alice")
);
Evaluation Timing

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.

Dependent Default Parameter
function calculateTotal(
  price,
  quantity = 1,
  tax = price *
    quantity *
    0.2
) {
  return (
    price *
    quantity +
    tax
  );
}

console.log(
  calculateTotal(
    100,
    2
  )
);
Output
240
Declaration Order Matters

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.

Required Parameter Pattern
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
  )
);
Output
{
  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.

Basic Rest Parameter
function showValues(
  ...values
) {
  console.log(values);
}

showValues(
  10,
  20,
  30,
  40
);
Output
[10, 20, 30, 40]

Rest Parameters Are Real Arrays

Rest parameters support array methods such as map(), filter(), and reduce() directly.

Sum with a Rest Parameter
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)
);
Output
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.

Named and Rest Parameters
function createMessage(
  sender,
  ...recipients
) {
  return {
    sender,
    recipients
  };
}

console.log(
  createMessage(
    "Alice",
    "Bob",
    "Maya",
    "David"
  )
);
Output
{
  sender: "Alice",
  recipients: [
    "Bob",
    "Maya",
    "David"
  ]
}

The Rest Parameter Must Be Last

Invalid Rest Parameter Placement
// 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

Arrow Function Rest Parameter
const multiplyAll =
  (...numbers) =>
    numbers.reduce(
      (product, number) =>
        product * number,
      1
    );

console.log(
  multiplyAll(
    2,
    3,
    4
  )
);
Output
24

Filtering Rest Arguments

Process Valid Numbers
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
  )
);
Output
30

Rest Syntax in Parameters

In a function definition, rest syntax collects separate arguments into one array.

Collect Arguments
function collect(
  ...items
) {
  return items;
}

const result =
  collect(
    "HTML",
    "CSS",
    "JavaScript"
  );

console.log(result);
Output
["HTML", "CSS", "JavaScript"]

Spread Syntax in Function Calls

In a function call, spread syntax expands an iterable into separate arguments.

Spread Array Arguments
function add(
  first,
  second,
  third
) {
  return (
    first +
    second +
    third
  );
}

const numbers = [
  10,
  20,
  30
];

console.log(
  add(...numbers)
);
Output
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

Mixed Function Arguments
function createRange(
  start,
  middle,
  end
) {
  return [
    start,
    middle,
    end
  ];
}

const middleValues = [
  5
];

console.log(
  createRange(
    1,
    ...middleValues,
    10
  )
);
Output
[1, 5, 10]

Math Methods with Spread Syntax

Spread syntax is commonly used to pass array values to methods expecting separate numeric arguments.

Math.min() and Math.max()
const scores = [
  82,
  95,
  71,
  88
];

const lowest =
  Math.min(...scores);

const highest =
  Math.max(...scores);

console.log(lowest);
console.log(highest);
Output
71
95
Very Large Arrays

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.

Argument Forwarding
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
);
Output
240

Combining Several Arrays as Arguments

Spread Multiple Arrays
function listSkills(
  ...skills
) {
  return skills.join(", ");
}

const frontend = [
  "HTML",
  "CSS"
];

const programming = [
  "JavaScript",
  "Python"
];

console.log(
  listSkills(
    ...frontend,
    ...programming,
    "SQL"
  )
);
Output
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.

Spread a String
function joinCharacters(
  ...characters
) {
  return characters.join("-");
}

console.log(
  joinCharacters(
    ..."Java"
  )
);
Output
J-a-v-a

Default and Rest Parameters Together

Configured Rest Processing
function formatValues(
  separator = ", ",
  ...values
) {
  return values.join(
    separator
  );
}

console.log(
  formatValues(
    " | ",
    "JavaScript",
    "Python",
    "SQL"
  )
);
Output
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 Parameter Count
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
);
Output
3
1
1
Do Not Treat length as Validation

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

Avoid These 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 length property enforces argument count.
Best Practice

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() and Math.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.

Store a Function in a Variable
function greet(name) {
  return `Hello, ${name}!`;
}

const sayHello = greet;

console.log(
  sayHello("Alice")
);

console.log(
  sayHello === greet
);
Output
Hello, Alice!
true
Function Reference vs Function Call

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.

Basic Callback
function processUser(
  name,
  callback
) {
  const normalizedName =
    name.trim();

  return callback(
    normalizedName
  );
}

function createGreeting(name) {
  return `Hello, ${name}!`;
}

console.log(
  processUser(
    " Alice ",
    createGreeting
  )
);
Output
Hello, Alice!

Passing a Function Correctly

Do Not Call the Callback Too Early
// 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.

Inline Arrow Callback
function transform(
  value,
  callback
) {
  return callback(value);
}

const result =
  transform(
    10,
    number =>
      number * 2
  );

console.log(result);
Output
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.

Array Method Callbacks
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);
Output
[2, 4, 6, 8, 10]
[2, 4]

Callbacks with Timers

Timer functions accept callbacks that are executed after a delay or at repeated intervals.

setTimeout()
console.log("Start");

setTimeout(
  () => {
    console.log(
      "Timer completed"
    );
  },
  1000
);

console.log("End");
Typical Output Order
Start
End
Timer completed
A Delay Is Not an Exact Execution Time

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 Receiving a Function
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
  )
);
Output
15
50

Returning a Function

A function can create and return another function with customized behavior.

Function Factory
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)
);
Output
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.

Basic Closure
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")
);
Output
Hello, Alice!
Welcome, Bob!

Closure Counter

Closures can preserve private state between function calls.

Private Counter State
function createCounter() {
  let count = 0;

  return function () {
    count += 1;

    return count;
  };
}

const counter =
  createCounter();

console.log(counter());
console.log(counter());
console.log(counter());
Output
1
2
3

Independent Closure State

Every call to the outer function creates a separate lexical environment.

Separate Counters
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()
);
Output
1
2
1

Closure with Several Methods

Encapsulated State
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()
);
Output
120
Encapsulation

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.

Loop Closure with let
const functions = [];

for (
  let index = 0;
  index < 3;
  index++
) {
  functions.push(
    () => index
  );
}

console.log(
  functions[0]()
);

console.log(
  functions[1]()
);

console.log(
  functions[2]()
);
Output
0
1
2
Older var Closure Problem

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.

Avoid Retaining Unnecessary Data

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.

Pure Function
function calculateTax(
  price,
  taxRate
) {
  return (
    price * taxRate
  );
}

console.log(
  calculateTax(
    100,
    0.2
  )
);

console.log(
  calculateTax(
    100,
    0.2
  )
);
Output
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

Pure Object Update
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);
Output
{
  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

External Mutation
let total = 0;

function addToTotal(
  amount
) {
  total += amount;

  return total;
}

console.log(
  addToTotal(10)
);

console.log(
  addToTotal(10)
);
Output
10
20

Separating Calculation from Side Effects

Keep calculations pure where practical, then perform external effects in a clearly identified location.

Separate Responsibilities
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);
Output
Total: $70

Function Composition

Function composition combines small functions so the result of one becomes the input of another.

Compose Functions
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 "
  )
);
Output
javascript-functions

Reusable pipe() Function

A pipeline applies functions from left to right.

Function Pipeline
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 "
  )
);
Output
javascript-closures

Partial Application

Partial application creates a new function by pre-filling some arguments of another function.

Pre-Fill Arguments
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
  )
);
Output
120

Memoization

Memoization stores previous results so repeated calls with the same input can return a cached value.

Simple Memoization
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));
Output
Calculating
25
25
Memoization Is Not Always Appropriate

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.

Recursive Countdown
function countdown(
  number
) {
  if (number <= 0) {
    console.log("Done");

    return;
  }

  console.log(number);

  countdown(
    number - 1
  );
}

countdown(3);
Output
3
2
1
Done

Recursive Factorial

Factorial Function
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)
);
Output
120

Recursive Array Processing

Flatten Nested Arrays
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
  ])
);
Output
[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.
Maximum 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.

Traditional IIFE
(function () {
  const message =
    "IIFE executed";

  console.log(message);
})();
Output
IIFE executed

Arrow Function IIFE

Arrow IIFE
(() => {
  const environment =
    "development";

  console.log(
    environment
  );
})();
Output
development

IIFE with Arguments and a Return Value

IIFE Result
const total =
  (function (
    price,
    quantity
  ) {
    return (
      price * quantity
    );
  })(25, 4);

console.log(total);
Output
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 IIFE
(async () => {
  try {
    const value =
      await Promise.resolve(
        "Data loaded"
      );

    console.log(value);
  } catch (error) {
    console.error(
      error.message
    );
  }
})();
Output
Data loaded

Common Callback and Closure Mistakes

Avoid These 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 var in 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.
Best Practice

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.

Method Context
const user = {
  name: "Alice",

  greet() {
    return `Hello, ${this.name}!`;
  }
};

console.log(
  user.greet()
);
Output
Hello, Alice!

The Call Site Determines this

The same function can produce different results when called through different objects.

Shared Method Function
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()
);
Output
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.

Immediate Calling Object
const company = {
  name: "Tech Corp",

  department: {
    name: "Engineering",

    getName() {
      return this.name;
    }
  }
};

console.log(
  company.department
    .getName()
);
Output
Engineering

Losing Method Context

Extracting a method into a standalone variable removes the original method call site.

Detached Method
"use strict";

const user = {
  name: "Alice",

  greet() {
    return `Hello, ${this.name}!`;
  }
};

const detachedGreet =
  user.greet;

console.log(
  detachedGreet()
);
Result

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.

Method Callback Problem
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.

Wrapper Callback
const user = {
  name: "Alice",

  greet() {
    console.log(
      `Hello, ${this.name}!`
    );
  }
};

setTimeout(
  () => {
    user.greet();
  },
  100
);
Output After the Delay
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.

Arrow Function Inside a Method
const user = {
  name: "Alice",

  greetLater() {
    setTimeout(
      () => {
        console.log(
          `Hello, ${this.name}!`
        );
      },
      100
    );
  }
};

user.greetLater();
Output After the Delay
Hello, Alice!

Arrow Function as an Object Method

Avoid this Pattern
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.call()
function greet(
  greeting,
  punctuation
) {
  return (
    `${greeting}, ` +
    `${this.name}${punctuation}`
  );
}

const user = {
  name: "Alice"
};

console.log(
  greet.call(
    user,
    "Hello",
    "!"
  )
);
Output
Hello, Alice!

Reusing a Function with call()

Explicit Context Reuse
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)
);
Output
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.apply()
function greet(
  greeting,
  punctuation
) {
  return (
    `${greeting}, ` +
    `${this.name}${punctuation}`
  );
}

const user = {
  name: "Alice"
};

const argumentsList = [
  "Welcome",
  "!"
];

console.log(
  greet.apply(
    user,
    argumentsList
  )
);
Output
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.

Spread Function Arguments
const numbers = [
  12,
  5,
  87,
  24
];

console.log(
  Math.max(...numbers)
);

console.log(
  Math.max.apply(
    null,
    numbers
  )
);
Output
87
87

bind()

The bind() method creates a new function with a fixed this value. It does not execute the original function immediately.

Function.bind()
function greet() {
  return `Hello, ${this.name}!`;
}

const user = {
  name: "Alice"
};

const boundGreet =
  greet.bind(user);

console.log(
  boundGreet()
);
Output
Hello, Alice!

Fixing a Detached Method with bind()

Bound Object Method
const user = {
  name: "Alice",

  greet() {
    return `Hello, ${this.name}!`;
  }
};

const detachedGreet =
  user.greet;

const boundGreet =
  user.greet.bind(user);

console.log(
  boundGreet()
);
Output
Hello, Alice!

Binding a Timer Callback

Bound Timer Method
const user = {
  name: "Alice",

  greet() {
    console.log(
      `Hello, ${this.name}!`
    );
  }
};

setTimeout(
  user.greet.bind(user),
  100
);
Output After the Delay
Hello, Alice!

Partial Application with bind()

Arguments supplied to bind() are placed before arguments supplied to the returned function.

Pre-Fill Function Arguments
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)
);
Output
20
30

A Bound Function Cannot Be Rebound

Calling bind() again does not replace the this value already fixed on a bound function.

Binding Is Permanent
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()
);
Output
Alice
Store Bound Listener References

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().

Borrow an Object Method
const user = {
  firstName: "Alice",
  lastName: "Smith",

  getFullName() {
    return (
      `${this.firstName} ` +
      this.lastName
    );
  }
};

const administrator = {
  firstName: "Maya",
  lastName: "Jones"
};

console.log(
  user.getFullName.call(
    administrator
  )
);
Output
Maya Jones

Borrowing Array Methods

Some array methods can operate on array-like objects when called with an explicit context.

Array-Like to Array
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)
);
Output
["JavaScript", "Python", "SQL"]
true
Modern Alternative

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 name and length
function calculateTotal(
  price,
  quantity,
  taxRate = 0.2
) {
  const subtotal =
    price * quantity;

  return (
    subtotal +
    subtotal * taxRate
  );
}

console.log(
  calculateTotal.name
);

console.log(
  calculateTotal.length
);
Output
calculateTotal
2
Function length

The length property counts parameters before the first default parameter. Rest parameters are not counted.

Custom Function Properties

Function Metadata
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
);
Output
$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.

Constructor Function
function User(
  name,
  age
) {
  this.name = name;
  this.age = age;
  this.active = true;
}

const alice =
  new User(
    "Alice",
    30
  );

console.log(alice);
Output
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.

Prototype Method
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
);
Output
Hello, Alice!
Hello, Bob!
true

Checking Constructor Instances

instanceof
function User(name) {
  this.name = name;
}

const alice =
  new User("Alice");

console.log(
  alice instanceof User
);

console.log(
  alice instanceof Object
);
Output
true
true

Forgetting new

Constructor Invocation Mistake
"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.

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);
Output
true
Alice

Constructor Return Behavior

Returning a primitive from a constructor is normally ignored. Returning an object explicitly replaces the newly created instance.

Explicit Object Return
function Product(name) {
  this.name = name;

  return {
    replacement: true
  };
}

const product =
  new Product(
    "Keyboard"
  );

console.log(product);
Output
{
  replacement: true
}

Arrow Functions Cannot Be Constructors

Invalid Constructor
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.

Factory Function
function createUser(
  name,
  age
) {
  return {
    name,
    age,
    active: true,

    greet() {
      return (
        `Hello, ${this.name}!`
      );
    }
  };
}

const alice =
  createUser(
    "Alice",
    30
  );

console.log(
  alice.greet()
);
Output
Hello, Alice!

Factory Function with Closure State

Private Factory 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()
);
Output
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.

Equivalent Class Syntax
class User {
  constructor(name) {
    this.name = name;
  }

  greet() {
    return `Hello, ${this.name}!`;
  }
}

const alice =
  new User("Alice");

console.log(
  alice.greet()
);
Output
Hello, Alice!
Choosing an Object-Creation Pattern

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

Avoid These Mistakes
  • Assuming this is 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() or apply() 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 new when 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.
Best Practice

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 this values usually depend on the call site.
  • Method calls assign this to the object before the dot.
  • Detached methods lose their original object context.
  • Arrow functions inherit this from 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 name and length.
  • 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.

Basic Generator
function* createIds() {
  let id = 1;

  while (true) {
    yield id++;
  }
}

const ids = createIds();

console.log(ids.next().value); // 1
console.log(ids.next().value); // 2
FeaturePurpose
function*Declares a generator function.
yield valuePauses execution and returns a value.
iterator.next()Resumes execution and returns { value, done }.
yield*Delegates to another iterable.
Generators are useful for lazy sequences and custom iterators. For asynchronous operations, use the dedicated Promises and async/await chapters later in this cheat sheet.

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.

Basic if Statement
const age = 20;

if (age >= 18) {
  console.log(
    "Access granted"
  );
}
Output
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.

Skipped if Block
const age = 16;

console.log(
  "Checking access"
);

if (age >= 18) {
  console.log(
    "Access granted"
  );
}

console.log(
  "Check complete"
);
Output
Checking access
Check complete

Conditions Produce Boolean-Like Decisions

Comparison expressions commonly produce the Boolean values true or false.

Store a Comparison Result
const score = 85;

const hasPassed =
  score >= 60;

console.log(hasPassed);

if (hasPassed) {
  console.log(
    "You passed"
  );
}
Output
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.

Direct Boolean Check
const isLoggedIn = true;

if (isLoggedIn) {
  console.log(
    "Welcome back"
  );
}
Output
Welcome back
Prefer Direct Boolean Checks
// 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.

Negated Condition
const isLoggedIn = false;

if (!isLoggedIn) {
  console.log(
    "Please log in"
  );
}
Output
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.

Strict Equality Check
const enteredCode =
  "1234";

if (
  enteredCode === "1234"
) {
  console.log(
    "Correct code"
  );
}

console.log(
  5 === 5
);

console.log(
  5 === "5"
);
Output
Correct code
true
false

Loose Equality

The loose equality operator == may convert one or both values before comparing them. This can produce surprising results.

Equality Coercion
console.log(
  5 == "5"
);

console.log(
  false == 0
);

console.log(
  "" == 0
);

console.log(
  null == undefined
);
Output
true
true
true
true
Prefer Strict Equality

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.

Strict Inequality Check
const status =
  "pending";

if (
  status !== "complete"
) {
  console.log(
    "Work remains"
  );
}

console.log(
  10 !== "10"
);
Output
Work remains
true

Numeric Range Conditions

Relational operators are commonly used to check whether a number is above, below, or equal to a boundary.

Minimum Score Check
const score = 75;
const passingScore = 60;

if (
  score >= passingScore
) {
  console.log(
    "Passing score"
  );
}
Output
Passing score

String Comparisons

Strings are compared according to Unicode code-unit order. Comparisons are case-sensitive.

Case-Sensitive String Comparison
const role = "Admin";

console.log(
  role === "Admin"
);

console.log(
  role === "admin"
);

if (
  role.toLowerCase() ===
  "admin"
) {
  console.log(
    "Administrator detected"
  );
}
Output
true
false
Administrator detected

Normalize Before Comparing User Input

User-entered text often contains inconsistent capitalization or whitespace. Normalize the value before comparing it.

Normalize Text Input
const enteredAnswer =
  "  JAVASCRIPT  ";

const normalizedAnswer =
  enteredAnswer
    .trim()
    .toLowerCase();

if (
  normalizedAnswer ===
  "javascript"
) {
  console.log(
    "Correct answer"
  );
}
Output
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.

Date Comparison
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()
);
Output
Valid date range
false
true

Objects Compare by Reference

Strict equality compares object references rather than property contents.

Object Reference Comparison
const firstUser = {
  name: "Alice"
};

const secondUser = {
  name: "Alice"
};

const sameUser =
  firstUser;

console.log(
  firstUser === secondUser
);

console.log(
  firstUser === sameUser
);
Output
false
true

Block Scope Inside if

Variables declared with let or const inside an if block are available only inside that block.

Conditional Block Scope
const isAdmin = true;

if (isAdmin) {
  const message =
    "Admin access";

  console.log(message);
}

// ReferenceError
// console.log(message);
Output
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.

Outer Variable Assignment
const score = 85;
let result;

if (score >= 60) {
  result = "Passed";
}

console.log(result);
Output
Passed

Braces Around Conditional Blocks

JavaScript permits a single statement without braces, but using braces is safer and easier to maintain.

Avoid Omitting Braces
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.

Recommended Braced Block
const isActive = true;

if (isActive) {
  console.log(
    "Active"
  );

  console.log(
    "Condition confirmed"
  );
}
Output
Active
Condition confirmed

Assignment vs Comparison

Common Conditional Mistake
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.

Boolean Validation Function
function isValidAge(
  age
) {
  return (
    Number.isInteger(age) &&
    age >= 18
  );
}

const age = 30;

if (isValidAge(age)) {
  console.log(
    "Valid adult age"
  );
}
Output
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

Avoid These 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.
Best Practice

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 if block 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.
  • let and const are 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.

Basic if...else
const age = 16;

if (age >= 18) {
  console.log(
    "Access granted"
  );
} else {
  console.log(
    "Access denied"
  );
}
Output
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.

Two Possible Outcomes
const isOnline = true;

if (isOnline) {
  console.log(
    "User is online"
  );
} else {
  console.log(
    "User is offline"
  );
}
Output
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.

Conditional Assignment
const score = 72;
let result;

if (score >= 60) {
  result = "Passed";
} else {
  result = "Failed";
}

console.log(result);
Output
Passed

Returning from if...else Branches

A function can return a different result from each branch.

Conditional Return Value
function getAccessMessage(
  isLoggedIn
) {
  if (isLoggedIn) {
    return "Welcome back";
  } else {
    return "Please log in";
  }
}

console.log(
  getAccessMessage(true)
);

console.log(
  getAccessMessage(false)
);
Output
Welcome back
Please log in
else May Be Unnecessary After return

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.

Multiple Conditions
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"
  );
}
Output
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.

First Matching Branch
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");
}
Output
Hot

Branch Order Matters

Place more specific or higher-threshold conditions before broader conditions that would also match the same value.

Incorrect Branch Order
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.

Correct Branch Order
const score = 95;

if (score >= 90) {
  console.log("Excellent");
} else if (score >= 60) {
  console.log("Passed");
} else {
  console.log("Failed");
}
Output
Excellent

Grading Example

Validate the acceptable range before assigning a grade.

Grade Calculator
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)
);
Output
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.

Early Return Grade Function
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)
);
Output
B

Independent if Statements vs else if

Separate if statements may all run. An if...else if chain selects only the first matching branch.

Independent Conditions
const number = 12;

if (number > 0) {
  console.log("Positive");
}

if (number % 2 === 0) {
  console.log("Even");
}

if (number > 10) {
  console.log(
    "Greater than ten"
  );
}
Output
Positive
Even
Greater than ten
Exclusive Branch Chain
const number = 12;

if (number < 0) {
  console.log("Negative");
} else if (number === 0) {
  console.log("Zero");
} else {
  console.log("Positive");
}
Output
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

Status Branches
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"
  )
);
Output
Account awaiting approval

Normalizing Before Branching

Normalize user input once before checking multiple possible values.

Normalized Status Check
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 "
  )
);
Output
Account active

Price-Based Discount Example

Discount Thresholds
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);
Output
0.1
30

Boundary Conditions

Decide carefully whether a boundary value should be included with >= or excluded with >.

Inclusive Age Boundary
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)
);
Output
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.

Review Overlapping Ranges
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.

Unreachable Branch
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.

Fallback Branch
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"
  )
);
Output
Unsupported theme

When No Final else Is Needed

A final else is optional. Omit it when unmatched values should intentionally produce no action.

Optional 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"
);
Output
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.

Named Boolean Conditions
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"
  );
}
Output
Editor access granted

else and else if Common Mistakes

Avoid These Mistakes
  • Placing broad conditions before more specific conditions.
  • Creating an unreachable else if branch.
  • Using an else if chain when several conditions should all run.
  • Using separate if statements 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 else after 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.
Best Practice

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

  • else provides an alternative when an if condition is falsy.
  • else if adds 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 if statements can all execute.
  • An else if chain selects one branch.
  • Boundary operators determine whether threshold values are included.
  • A final else handles every unmatched value.
  • Early returns can remove unnecessary nesting and else blocks.
  • 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.

Basic Nested Condition
const isLoggedIn = true;
const isAdmin = true;

if (isLoggedIn) {
  console.log(
    "User authenticated"
  );

  if (isAdmin) {
    console.log(
      "Admin access granted"
    );
  }
}
Output
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

Nested Access Check
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"
  );
}
Output
Verify your account

The Dangling else Problem

Without braces, an else belongs to the nearest unmatched if. Braces make the intended structure explicit.

Avoid Ambiguous Nesting
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.

Deeply Nested Example
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.

Basic Guard Clause
function greetUser(user) {
  if (!user) {
    return "User unavailable";
  }

  return `Hello, ${user.name}!`;
}

console.log(
  greetUser(null)
);

console.log(
  greetUser({
    name: "Alice"
  })
);
Output
User unavailable
Hello, Alice!

Reducing Nesting with Guard Clauses

Flattened Order Validation
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
    }
  )
);
Output
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.

Early Return Validation
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)
);
Output
120
0

Return Ends Function Execution

Unreachable Code After return
function checkValue(
  value
) {
  if (value < 0) {
    return "Negative";
  }

  console.log(
    "Validation passed"
  );

  return "Valid";
}

console.log(
  checkValue(-5)
);

console.log(
  checkValue(10)
);
Output
Negative
Validation passed
Valid

Early Throw

Throw an error when invalid input violates the function contract and the function cannot continue meaningfully.

Guard Clause with throw
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
  )
);
Output
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

Validation Result Pattern
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"
  )
);
Output
{
  valid: true,
  value: "Alice"
}

Validation Order Matters

Validate broad structural requirements before accessing methods or properties that depend on them.

Safe Validation Sequence
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 "
  )
);
Output
alice@example.com
Do Not Use String Methods Before Type Validation
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

Object Validation Flow
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"
    }
  })
);
Output
London

Optional Chaining as a Compact Alternative

Optional chaining can simplify safe property access when detailed error messages are not required.

Optional Chaining Guard
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"
    }
  })
);
Output
Unknown
London

Named Boolean Conditions

Complex conditional expressions become easier to understand when their parts are assigned meaningful names.

Readable Access Conditions
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"
    }
  )
);
Output
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

Hard-to-Read Condition
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.

Reusable Eligibility Rule
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"
  );
}
Output
Discount available

Early Continue in Loops

The continue statement skips the remaining work for the current loop iteration and moves to the next item.

Skip Invalid Values
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);
Output
70

Reducing Loop Nesting with continue

Flat Loop Processing
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}`
  );
}
Output
Processing Alice

Early break in Loops

The break statement ends the nearest loop immediately.

Stop After a Match
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);
Output
{
  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

filter(), some() and every()
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
);
Conceptual Output
[
  {
    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.

Status Lookup Object
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"
  )
);
Output
Account pending
Unknown status

Handler Lookup Pattern

Conditional Function Lookup
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")
);
Output
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

Avoid These 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.
Best Practice

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.
  • continue skips the current loop iteration.
  • break ends 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.

Basic for Loop
for (
  let number = 1;
  number <= 5;
  number += 1
) {
  console.log(number);
}
Output
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

Loop Execution Trace
for (
  let index = 0;
  index < 3;
  index += 1
) {
  console.log(
    `Iteration ${index}`
  );
}

console.log(
  "Loop complete"
);
Output
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.

Zero-Based Counter
for (
  let index = 0;
  index < 4;
  index += 1
) {
  console.log(index);
}
Output
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.

Array Index Loop
const languages = [
  "JavaScript",
  "Python",
  "SQL"
];

for (
  let index = 0;
  index < languages.length;
  index += 1
) {
  console.log(
    languages[index]
  );
}
Output
JavaScript
Python
SQL

Common Off-by-One Error

Incorrect Array Boundary
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.

Conceptual Output
A
B
C
undefined

Counting Up

Incrementing Counter
for (
  let number = 2;
  number <= 10;
  number += 2
) {
  console.log(number);
}
Output
2
4
6
8
10

Counting Down

Decrementing Counter
for (
  let number = 5;
  number >= 1;
  number -= 1
) {
  console.log(number);
}

console.log(
  "Complete"
);
Output
5
4
3
2
1
Complete

Custom Step Values

The update expression can increase or decrease the counter by any suitable amount.

Increment by Five
for (
  let number = 0;
  number <= 20;
  number += 5
) {
  console.log(number);
}
Output
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.
Update Expression Style

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.

Numeric Accumulator
let total = 0;

for (
  let number = 1;
  number <= 5;
  number += 1
) {
  total += number;
}

console.log(total);
Output
15

Calculating an Array Total

Sum Array Values
const prices = [
  25,
  40,
  15,
  20
];

let total = 0;

for (
  let index = 0;
  index < prices.length;
  index += 1
) {
  total += prices[index];
}

console.log(total);
Output
100

Building a New Array

Transform Array Values
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
);
Output
[2, 4, 6, 8]

Filtering Values with a Loop

Collect Even Numbers
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
);
Output
[2, 4, 6]

Accessing Both Index and Value

Indexed Array Output
const languages = [
  "JavaScript",
  "Python",
  "SQL"
];

for (
  let index = 0;
  index < languages.length;
  index += 1
) {
  console.log(
    `${index}: ${languages[index]}`
  );
}
Output
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.

Update Array Elements
const numbers = [
  1,
  2,
  3
];

for (
  let index = 0;
  index < numbers.length;
  index += 1
) {
  numbers[index] *= 2;
}

console.log(numbers);
Output
[2, 4, 6]
Mutation Warning

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.

Block-Scoped Counter
for (
  let index = 0;
  index < 3;
  index += 1
) {
  console.log(index);
}

// ReferenceError
// console.log(index);
Output
0
1
2

Using an Existing Counter Variable

Declare the counter outside the loop only when its final value is genuinely needed afterward.

Outer Counter Variable
let index = 0;

for (
  ;
  index < 3;
  index += 1
) {
  console.log(index);
}

console.log(
  `Final index: ${index}`
);
Output
0
1
2
Final index: 3

Multiple Loop Variables

A for loop can initialize and update several variables using commas.

Two Loop Counters
for (
  let left = 0,
      right = 4;
  left < right;
  left += 1,
      right -= 1
) {
  console.log(
    left,
    right
  );
}
Output
0 4
1 3

Omitting for Loop Expressions

The initializer, condition, and update expressions are all optional, but the two semicolons must remain.

Omitted Initializer
let index = 0;

for (
  ;
  index < 3;
  index += 1
) {
  console.log(index);
}
Output
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.

Controlled Infinite Loop
let number = 1;

for (;;) {
  console.log(number);

  if (number === 3) {
    break;
  }

  number += 1;
}
Output
1
2
3
Infinite Loop Risk

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

Counter Moves in the Wrong Direction
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

Counter Never Changes
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.

Unexpected Semicolon
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

Multiplication Sequence
const multiplier = 5;

for (
  let number = 1;
  number <= 10;
  number += 1
) {
  const result =
    multiplier * number;

  console.log(
    `${multiplier} × ${number} = ${result}`
  );
}
Output
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

Build Repeated Text
let output = "";

for (
  let number = 1;
  number <= 5;
  number += 1
) {
  output += `${number}`;

  if (number < 5) {
    output += ", ";
  }
}

console.log(output);
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

Avoid These Mistakes
  • Using <= array.length when 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 var when block scope is expected.
  • Accessing the counter outside its let scope.
  • 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.
Best Practice

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 for loop 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 let are 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.

Condition-Controlled Loops
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.

Iterate Over Values
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.

Iterate Over Object Keys
const user = {
  name: "Maya",
  role: "Developer"
};

for (const key in user) {
  if (Object.hasOwn(user, key)) {
    console.log(key, user[key]);
  }
}
Quick rule: Use 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

Outer and Inner Iterations
for (
  let row = 1;
  row <= 3;
  row += 1
) {
  for (
    let column = 1;
    column <= 2;
    column += 1
  ) {
    console.log(
      `Row ${row}, Column ${column}`
    );
  }
}
Output
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.

Count Nested Iterations
let iterations = 0;

for (
  let outer = 0;
  outer < 3;
  outer += 1
) {
  for (
    let inner = 0;
    inner < 4;
    inner += 1
  ) {
    iterations += 1;
  }
}

console.log(iterations);
Output
12
Iteration Formula

For two fixed-length loops, the approximate number of inner-body executions is: outer iterations × inner iterations.

Creating a Grid

Grid Coordinates
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})`
    );
  }
}
Output
(0, 0)
(0, 1)
(0, 2)
(1, 0)
(1, 1)
(1, 2)
(2, 0)
(2, 1)
(2, 2)

Building Grid Rows

Create a Text Grid
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);
}
Output
#####
#####
#####

Creating a Triangle Pattern

Increasing Inner Loop Length
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);
}
Output
*
**
***
****
*****

Processing a Matrix

A matrix is commonly represented as an array containing other arrays.

Read Matrix Values
const matrix = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
];

for (
  const row of matrix
) {
  for (
    const value of row
  ) {
    console.log(value);
  }
}
Output
1
2
3
4
5
6
7
8
9

Matrix Indexes and Values

Matrix Coordinates
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}`
    );
  }
}
Output
[0][0] = 10
[0][1] = 20
[1][0] = 30
[1][1] = 40

Summing a Matrix

Matrix Total
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);
Output
21

Row Totals

Calculate Each Row Total
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);
Output
[6, 15, 24]

Comparing Two Arrays

Nested loops can compare every value in one array with every value in another array.

Find Shared Values
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);
Output
[2, 3]
Faster Lookup for Large Collections

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

Cartesian Product
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
);
Output
[
  "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.

Unique Array Pairs
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);
Output
[
  ["Alice", "Bob"],
  ["Alice", "Maya"],
  ["Bob", "Maya"]
]

Finding Duplicate Values

Manual Duplicate Comparison
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);
Output
B

Breaking the Inner Loop

An ordinary break exits only the closest loop containing it. The outer loop continues.

Inner break Behavior
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}`
  );
}
Output
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.

Flag-Controlled Outer Exit
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);
Output
{
  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.

Return a Matrix Position
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
  )
);
Output
{
  row: 1,
  column: 1
}
Return vs Flag

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.

Skip One Matrix Value
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);
  }
}
Output
2
4
6

Skipping an Entire Outer Iteration

A condition before the inner loop can skip the complete group represented by the outer iteration.

Skip an Invalid Matrix Row
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);
  }
}
Output
1
2
3
4
5
6

Skipping Invalid Matrix Values

Validate Rows and 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);
Output
10

Nested Loop Variable Scope

Counters declared with let or const belong to their own loop blocks.

Separate Loop Bindings
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

Confusing Shadowed Counters
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.

Jagged Array Iteration
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
      ]
    );
  }
}
Output
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

Nested Membership Search
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.

Set Membership Alternative
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
  );
}
Output
Alice
Maya

Nested Loops vs flatMap()

When the goal is to create combinations or flatten transformed groups, flatMap() may express the result more directly.

Create Combinations with flatMap()
const colors = [
  "Red",
  "Blue"
];

const sizes = [
  "Small",
  "Large"
];

const combinations =
  colors.flatMap(
    color =>
      sizes.map(
        size =>
          `${color} - ${size}`
      )
  );

console.log(
  combinations
);
Output
[
  "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

Avoid These Mistakes
  • Expecting an ordinary break to exit every loop level.
  • Expecting an ordinary continue to 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.
Best Practice

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...of loops.
  • Nested entries() loops provide row and column indexes.
  • An ordinary break exits only the nearest loop.
  • An ordinary continue affects only the nearest loop.
  • A flag can communicate an inner-loop result to the outer loop.
  • An early return can exit all nested loops inside a function.
  • Starting an inner index at outerIndex + 1 creates 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

Compare break and continue
for (
  let number = 1;
  number <= 5;
  number += 1
) {
  if (number === 2) {
    continue;
  }

  if (number === 4) {
    break;
  }

  console.log(number);
}

console.log(
  "Loop complete"
);
Output
1
3
Loop complete
Execution Result

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.

Search with break
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
  );
}
Search with return
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.

Expected Loop Exit
const values = [
  10,
  20,
  "stop",
  30
];

let total = 0;

for (
  const value of values
) {
  if (value === "stop") {
    break;
  }

  total += value;
}

console.log(total);
Output
30
Invalid Data Error
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

Exit a Matrix Search
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);
Output
{
  row: 1,
  column: 1
}

Labeled continue Example

Skip an Invalid Group
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
);
Output
[
  [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.

Unsafe while Loop
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.

Safe while Loop
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);
}
Output
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.

Descriptive Search Flag
const numbers = [
  3,
  7,
  12,
  15
];

let hasEvenNumber =
  false;

for (
  const number of numbers
) {
  if (
    number % 2 === 0
  ) {
    hasEvenNumber = true;
    break;
  }
}

console.log(
  hasEvenNumber
);
Output
true
Avoid Ambiguous Flags
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.

Cannot break from forEach()
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.

Callback return Behavior
const numbers = [
  1,
  2,
  3,
  4
];

numbers.forEach(
  number => {
    if (number === 2) {
      return;
    }

    console.log(number);
  }
);
Output
1
3
4
Callback return Resembles continue

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.

Readable Validation Guards
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
  );
}
Output
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

Avoid These Mistakes
  • Using continue when the complete loop should stop.
  • Using break when only one value should be skipped.
  • Expecting break to 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 flag or status.
  • 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.
Best Practice

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

  • break ends the nearest loop or switch.
  • continue skips 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.
  • return ends the complete function and may provide a result.
  • throw reports 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

Clear Index-Based Loop
const values = [
  10,
  20,
  30
];

for (
  let index = 0;
  index < values.length;
  index += 1
) {
  const value =
    values[index];

  console.log(
    index,
    value
  );
}
Output
0 10
1 20
2 30

Loop Best Practices Checklist

Write Loops That Are Easy to Verify
  • Use descriptive names such as index, row, column, or item.
  • Make the starting value, condition, and update easy to see.
  • Update a counter in one predictable place whenever possible.
  • Use index < array.length for 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 break when processing should stop after a known condition.
  • Use continue sparingly 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 a for...in loop 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

Avoid Too Much Logic Inside One Loop
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

Use a Named Function
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.

Performance Rule

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 for when indexes or fixed iteration counts matter.
  • Use while when repetition depends on a condition.
  • Use do...while when the body must run at least once.
  • Use for...of for iterable values.
  • Use for...in for 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
Remember
  • Use for when you need an index.
  • Use while when you don't know how many iterations are needed.
  • Use do...while when the loop must execute at least once.
  • Use for...of for iterable values.
  • Use for...in for object keys.
  • Use break to exit a loop immediately.
  • Use continue to 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.

HTML Document
<body>
  <h1>Hello</h1>

  <p>Welcome!</p>
</body>
DOM Tree
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.

Select an Element
const heading =
document.querySelector("h1");

console.log(heading);
Output
<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()
Remember

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 document object.
  • 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

getElementById()
const title =
document.getElementById("title");

console.log(title);

Select the First Matching Element

querySelector()
const button =
document.querySelector(".btn");

console.log(button);

Select Multiple Elements

querySelectorAll()
const items =
document.querySelectorAll(".item");

console.log(items);
Output
NodeList(3)
Which Method Should You Use?
  • 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() and querySelectorAll() 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

textContent
const title =
document.querySelector("h1");

title.textContent =
"JavaScript Cheat Sheet";

Insert HTML

innerHTML
const box =
document.querySelector(".box");

box.innerHTML =
"<strong>Hello!</strong>";

Read Visible Text

innerText
const heading =
document.querySelector("h1");

console.log(
heading.innerText
);
Be Careful with innerHTML

Avoid inserting untrusted user input with innerHTML. It can introduce security risks such as Cross-Site Scripting (XSS).

Best Practice

Use textContent whenever you only need to display text. Use innerHTML only when you intentionally need to insert HTML.

Summary

  • textContent is the safest choice for text.
  • innerText returns visible text.
  • innerHTML reads and writes HTML markup.
  • Prefer textContent unless 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

getAttribute()
const image =
document.querySelector("img");

console.log(
image.getAttribute("src")
);

Set an Attribute

setAttribute()
const link =
document.querySelector("a");

link.setAttribute(
"target",
"_blank"
);

Remove an Attribute

removeAttribute()
const input =
document.querySelector("input");

input.removeAttribute(
"disabled"
);

Check if an Attribute Exists

hasAttribute()
const image =
document.querySelector("img");

console.log(
image.hasAttribute("alt")
);
Best Practice

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

add() & remove()
const button =
document.querySelector(".btn");

button.classList.add("active");

button.classList.remove("disabled");

Toggle a Class

toggle()
const menu =
document.querySelector(".menu");

menu.classList.toggle("open");

Check or Replace a Class

contains() & replace()
const card =
document.querySelector(".card");

if (
  card.classList.contains("dark")
) {
  card.classList.replace(
    "dark",
    "light"
  );
}
Best Practice

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 classList over 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

Modify CSS
const heading =
document.querySelector("h1");

heading.style.color =
"royalblue";

heading.style.fontSize =
"36px";

Show or Hide an Element

display
const menu =
document.querySelector(".menu");

menu.style.display =
"none";

Read an Inline Style

Read a Style Value
const box =
document.querySelector(".box");

console.log(
box.style.width
);
Important

element.style only accesses inline styles. Styles applied through external CSS files are not returned unless you use getComputedStyle().

Best Practice

Use style for small, temporary changes. For themes, states, and larger visual updates, prefer classList and let CSS handle the styling.

Summary

  • element.style modifies inline CSS.
  • Property names use camelCase (for example backgroundColor).
  • Use CSS units such as px when required.
  • display = "none" hides an element.
  • Prefer classList for 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

createElement() + append()
const paragraph =
document.createElement("p");

paragraph.textContent =
"Hello, DOM!";

document.body.append(
paragraph
);

Append an Existing Element

appendChild()
const list =
document.querySelector("ul");

const item =
document.createElement("li");

item.textContent =
"JavaScript";

list.appendChild(item);

Insert at the Beginning

prepend()
const list =
document.querySelector("ul");

const item =
document.createElement("li");

item.textContent =
"First Item";

list.prepend(item);
Best Practice

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

remove()
const message =
document.querySelector(".message");

message.remove();

Replace an Element

replaceWith()
const oldHeading =
document.querySelector("h1");

const newHeading =
document.createElement("h2");

newHeading.textContent =
"JavaScript DOM";

oldHeading.replaceWith(
newHeading
);
Best Practice

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.

Remember

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

Traversing Example
const item =
document.querySelector(".item");

console.log(
item.parentElement
);

console.log(
item.nextElementSibling
);

console.log(
item.previousElementSibling
);

Access Child Elements

children
const list =
document.querySelector("ul");

console.log(
list.children
);

console.log(
list.firstElementChild
);

console.log(
list.lastElementChild
);
Best Practice

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

  • parentElement moves up the DOM tree.
  • children returns all child elements.
  • firstElementChild and lastElementChild access the first and last child.
  • nextElementSibling and previousElementSibling move 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
Quick Tip

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

Click Event
const button =
document.querySelector("button");

button.addEventListener(
  "click",
  () => {
    console.log("Button clicked!");
  }
);
Output
Button clicked!
Best Practice

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.
  • click is 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

Basic Syntax
element.addEventListener(
  "event",
  callback
);

Click Event

Button Click
const button =
document.querySelector("button");

button.addEventListener(
  "click",
  () => {
    console.log("Clicked!");
  }
);
Output
Clicked!

Using a Named Function

Named Callback
const button =
document.querySelector("button");

function showMessage() {
  console.log("Hello!");
}

button.addEventListener(
  "click",
  showMessage
);

Removing an Event Listener

removeEventListener()
button.removeEventListener(
  "click",
  showMessage
);
Important

Anonymous arrow functions cannot be removed with removeEventListener(). Use a named function when you plan to remove the listener later.

Best Practice

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

Multiple Event Listeners
const input =
document.querySelector("input");

input.addEventListener(
  "input",
  () => {
    console.log("Typing...");
  }
);

input.addEventListener(
  "change",
  () => {
    console.log("Value changed");
  }
);
Output
Typing...
Value changed
Quick Tip

Use input when you need updates while the user types. Use change when you only need the final value after editing is complete.

Summary

  • click is the most commonly used event.
  • input fires continuously while typing.
  • change fires after editing is complete.
  • submit handles form submissions.
  • keydown and keyup respond to keyboard input.
  • mouseenter and mouseleave are 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

Reading Event Data
const button =
document.querySelector("button");

button.addEventListener(
  "click",
  (event) => {
    console.log(event.type);
    console.log(event.target);
  }
);
Output
click
<button>...</button>

Prevent Default Behavior

preventDefault()
const form =
document.querySelector("form");

form.addEventListener(
  "submit",
  (event) => {
    event.preventDefault();

    console.log(
      "Form prevented"
    );
  }
);
Best Practice

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.target identifies the element that fired the event.
  • event.type returns the event name.
  • event.key is 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

Bubbling
const parent =
document.querySelector(".parent");

const child =
document.querySelector(".child");

parent.addEventListener("click", () => {
  console.log("Parent");
});

child.addEventListener("click", () => {
  console.log("Child");
});
Clicking the child outputs:
Child
Parent

Stop Bubbling

stopPropagation()
child.addEventListener(
  "click",
  (event) => {
    event.stopPropagation();

    console.log("Child");
  }
);
Best Practice

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

Delegating Click Events
const list =
document.querySelector("ul");

list.addEventListener(
  "click",
  (event) => {
    if (
      event.target.matches("li")
    ) {
      console.log(
        event.target.textContent
      );
    }
  }
);
Output
Clicked list item
Best Practice

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.target to 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()
Quick Tip

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

setTimeout()
console.log("Start");

setTimeout(() => {
  console.log("Finished");
}, 2000);

console.log("End");
Output
Start
End
Finished
Remember

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/await build 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

Pass a Function as an Argument
function processUser(name, callback) {
  console.log(`Hello, ${name}`);

  callback();
}

function finish() {
  console.log("Finished");
}

processUser("Alice", finish);
Output
Hello, Alice
Finished

Asynchronous Callback

Callback with setTimeout()
function loadData(callback) {
  setTimeout(() => {
    const data = {
      id: 1,
      name: "Alice"
    };

    callback(data);
  }, 1000);
}

loadData((data) => {
  console.log(data);
});
Output after one second
{
  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.
Avoid Deeply Nested Callbacks
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.

Best Practice

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

resolve() and reject()
const request = new Promise(
  (resolve, reject) => {
    const success = true;

    if (success) {
      resolve("Data loaded");
    } else {
      reject(
        new Error("Request failed")
      );
    }
  }
);

Handle a Promise

then(), catch() and finally()
request
  .then((result) => {
    console.log(result);
  })
  .catch((error) => {
    console.error(
      error.message
    );
  })
  .finally(() => {
    console.log("Complete");
  });
Output
Data loaded
Complete

Promise Chaining

Return Values Between Steps
Promise.resolve(5)
  .then((number) => {
    return number * 2;
  })
  .then((number) => {
    console.log(number);
  })
  .catch((error) => {
    console.error(error);
  });
Output
10
Return Values from then()

Return the next value or Promise from each then() callback. Without return, the next step receives undefined.

Best Practice

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
async function getMessage() {
  return "Hello!";
}

getMessage().then((message) => {
  console.log(message);
});
Output
Hello!

Wait for a Promise

Using await
function loadMessage() {
  return Promise.resolve(
    "Data loaded"
  );
}

async function showMessage() {
  const message =
    await loadMessage();

  console.log(message);
}

showMessage();
Output
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

Multiple await Expressions
async function loadProfile() {
  const user =
    await getUser();

  const posts =
    await getPosts(user.id);

  console.log(user, posts);
}

loadProfile();
await Requires an Async Context

Use await inside an async function. Top-level await is also available in JavaScript modules.

Best Practice

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 async function always returns a Promise.
  • await waits 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...catch to 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

Promise.all()
async function loadDashboard() {
  const [user, posts] =
    await Promise.all([
      getUser(),
      getPosts()
    ]);

  console.log(user, posts);
}

loadDashboard();

Keep Every Result

Promise.allSettled()
const results =
  await Promise.allSettled([
    Promise.resolve("Loaded"),
    Promise.reject(
      new Error("Failed")
    )
  ]);

console.log(results);
Result Structure
[
  {
    status: "fulfilled",
    value: "Loaded"
  },
  {
    status: "rejected",
    reason: Error("Failed")
  }
]

Use the First Result

race() and any()
const firstSettled =
  await Promise.race([
    requestA(),
    requestB()
  ]);

const firstSuccessful =
  await Promise.any([
    requestA(),
    requestB()
  ]);
Promise.all() Fails Fast

If one Promise rejects, Promise.all() rejects immediately. Use Promise.allSettled() when every outcome must be collected.

Best Practice

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()
Quick Tip

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.
  • async and await simplify Promise handling.
  • Use try...catch for 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

JSON Object
{
  "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.
Remember

JSON is text, not a JavaScript object. It must follow strict syntax, including double quotes around property names and string values.

Best Practice

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

JSON.parse()
const json = `{
  "name": "Alice",
  "age": 25
}`;

const user =
  JSON.parse(json);

console.log(user.name);
Output
Alice

Convert an Object to JSON Text

JSON.stringify()
const user = {
  name: "Alice",
  age: 25
};

const json =
  JSON.stringify(user);

console.log(json);
Output
{"name":"Alice","age":25}

Format JSON for Readability

Pretty-Printed JSON
const formatted =
  JSON.stringify(
    user,
    null,
    2
  );

console.log(formatted);
Output
{
  "name": "Alice",
  "age": 25
}
Invalid JSON Throws an Error

JSON.parse() throws a SyntaxError when the text is not valid JSON. Use try...catch when parsing data that may be malformed.

Best Practice

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

Access Properties
const json = `{
  "name": "Alice",
  "age": 25,
  "country": "USA"
}`;

const user =
  JSON.parse(json);

console.log(user.name);
console.log(user.country);
Output
Alice
USA

Loop Through JSON Arrays

JSON Array
const json = `[
  "HTML",
  "CSS",
  "JavaScript"
]`;

const skills =
  JSON.parse(json);

skills.forEach((skill) => {
  console.log(skill);
});
Output
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.
Remember

JSON data received from external sources may be invalid. Wrap JSON.parse() in try...catch when parsing untrusted data.

Best Practice

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...catch for 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
Quick Tip

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

Modules
project/
│
├── index.html
├── app.js
└── utils.js

Load a Module

HTML
<script type="module" src="app.js"></script>
Best Practice

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 export and import to 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

utils.js
export const appName =
  "CheatSheetSilo";

export function add(a, b) {
  return a + b;
}

export class User {
  constructor(name) {
    this.name = name;
  }
}

Export After Declaration

Export List
const taxRate = 0.25;

function calculateTax(price) {
  return price * taxRate;
}

export {
  taxRate,
  calculateTax
};

Default Export

formatter.js
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
One Default Export per Module

A module may contain many named exports but only one export default.

Best Practice

Prefer named exports when a file exposes several related values. Use a default export when the file clearly represents one primary value.

Summary

  • export shares 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

app.js
import {
  appName,
  add,
  User
} from "./utils.js";

console.log(appName);
console.log(add(2, 3));

const user =
  new User("Alice");

Import a Default Export

Default Import
import formatPrice
  from "./formatter.js";

console.log(
  formatPrice(19.99)
);

Rename an Import

Import Alias
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"
Use the Correct File Path

Browser modules normally require relative paths such as ./utils.js and the file extension should be included.

Best Practice

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 as to 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

Named Import
// utils.js
export function add(a, b) {
  return a + b;
}

// app.js
import { add }
from "./utils.js";

Default Export

Default Import
// formatter.js
export default
function format() {
  return "Done";
}

// app.js
import format
from "./formatter.js";
Best Practice

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
Quick Tip

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 export to share values.
  • Use import to 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

ReferenceError
console.log(userName);
Output
ReferenceError:
userName is not defined
Remember

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.

Best Practice

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, catch and finally.

Using try, catch and finally

The try, catch, and finally statements allow you to handle runtime errors without stopping your application.

Syntax

Basic Structure
try {
  // Code that may fail
} catch (error) {
  // Handle the error
} finally {
  // Always runs
}

Catch an Error

Handling Errors
try {
  console.log(userName);

} catch (error) {
  console.log(
    "Something went wrong."
  );
}
Output
Something went wrong.

Using finally

Cleanup Code
try {
  console.log("Loading...");

} finally {
  console.log("Finished");
}
Output
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.
Best Practice

Use finally for cleanup tasks such as hiding loading indicators or closing resources. Handle only the errors you expect.

Summary

  • try executes code that may fail.
  • catch handles runtime errors.
  • finally always 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

Basic Example
const age = -1;

if (age < 0) {
  throw new Error(
    "Age cannot be negative."
  );
}
Output
Error:
Age cannot be negative.

Throw and Catch

Custom Error Handling
try {

  throw new Error(
    "Something went wrong."
  );

} catch (error) {

  console.log(
    error.message
  );

}
Output
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.
Best Practice

Throw errors only for exceptional situations. Use clear, descriptive messages that help identify the problem quickly.

Summary

  • throw creates 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
Quick Tip

Handle expected errors with try...catch, throw meaningful errors when necessary, and avoid silently ignoring exceptions.

Error Handling Chapter Complete

  • Use try...catch to handle runtime errors.
  • finally always 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.