---
title: "JavaScript"
description: "JavaScript is a versatile, high-level programming language that powers the web. It supports object-oriented, functional, and event-driven programming paradigms."
author: "Mohammad Abu Mattar"
canonical: https://mkabumattar.com/cheatsheets/javascript
---

# JavaScript

JavaScript is a versatile, high-level programming language that powers the web. It supports object-oriented, functional, and event-driven programming paradigms.

Browse the sections below to explore JavaScript syntax, methods, and examples covering ES6+ features, async patterns, classes, modules, and more.

## Getting Started

Fundamental JavaScript concepts including variables, data types, and basic syntax.

### Variables

Declaring and using variables with var, let, and const.

**Keywords:** var, let, const, variable, declaration, scope, hoisting

#### Variable declarations

```javascript
var oldWay = 'function scoped';
let mutable = 'can be reassigned';
const immutable = 'cannot be reassigned';

let x = 1;
x = 2; // OK

const y = 1;
// y = 2; // TypeError: Assignment to constant variable
```

_exec_
```bash
node variables.js
```

let and const are block-scoped. var is function-scoped and hoisted. const prevents reassignment but does not make objects immutable.

- Prefer const by default, use let only when reassignment is needed.
- Avoid var in modern JavaScript.

#### Block scoping

```javascript
if (true) {
  var a = 1;
  let b = 2;
  const c = 3;
}
console.log(a); // 1
// console.log(b); // ReferenceError
// console.log(c); // ReferenceError
```

_exec_
```bash
node scope.js
```

_output_
```javascript
1
```

var leaks out of blocks, while let and const are confined to the block where they are declared.

- Block scoping prevents variable leaks and accidental overwrites.
- This is one of the main reasons to prefer let/const over var.

**Best practices:**

- Use const by default, let when you need to reassign.
- Never use var in modern code.
- Declare variables at the top of their scope for clarity.

**Common errors:**

- **Using const and then trying to reassign**: Use let if the variable needs to change.
- **Accessing let/const before declaration (temporal dead zone)**: Always declare variables before using them.

### Data Types

JavaScript primitive and reference types.

**Keywords:** string, number, boolean, , undefined, symbol, bigint, object, typeof

#### Primitive types

```javascript
const str = 'hello';          // string
const num = 42;               // number
const float = 3.14;           // number
const bool = true;            // boolean
const nothing = null;         // object (historical bug)
const undef = undefined;      // undefined
const sym = Symbol('id');     // symbol
const big = 9007199254740991n; // bigint

console.log(typeof str);      // "string"
console.log(typeof num);      // "number"
console.log(typeof nothing);  // "object"
```

_exec_
```bash
node types.js
```

_output_
```javascript
string
number
object
```

JavaScript has 7 primitive types. typeof null returning "object" is a well-known historical bug.

- Use === instead of == to avoid type coercion surprises.
- BigInt is for integers larger than Number.MAX_SAFE_INTEGER.

#### Type checking

```javascript
typeof 'hello'      // "string"
typeof 42           // "number"
typeof true         // "boolean"
typeof undefined    // "undefined"
typeof null         // "object" (bug)
typeof {}           // "object"
typeof []           // "object"
Array.isArray([])   // true
typeof function(){} // "function"
```

typeof works for most types but returns "object" for null and arrays. Use Array.isArray() for arrays.

- Use instanceof for checking class instances.
- null check: value === null.

**Best practices:**

- Use strict equality (===) to avoid implicit type coercion.
- Use Array.isArray() to check for arrays instead of typeof.
- Use Number.isNaN() instead of global isNaN().

**Common errors:**

- **Using typeof to check for null returns "object"**: Use value === null for explicit null checks.
- **Confusing undefined and null**: Use undefined for uninitialized, null for intentional absence of value.

### Template Literals

String interpolation and multiline strings using backticks.

**Keywords:** template, backtick, interpolation, string, multiline, tagged

#### String interpolation

```javascript
const name = 'World';
const greeting = `Hello, ${name}!`;
console.log(greeting);

const a = 10, b = 20;
console.log(`Sum: ${a + b}`);
console.log(`Type: ${typeof name}`);
```

_exec_
```bash
node template.js
```

_output_
```javascript
Hello, World!
Sum: 30
Type: string
```

Template literals use backticks and ${} for embedding expressions directly in strings.

- Any valid JavaScript expression can go inside ${}.
- Template literals preserve whitespace and newlines.

#### Multiline strings and tagged templates

```javascript
const multiline = `
  This is a
  multiline string
`;

function highlight(strings, ...values) {
  return strings.reduce((result, str, i) =>
    `${result}${str}<b>${values[i] || ''}</b>`, '');
}

const name = 'world';
console.log(highlight`Hello ${name}!`);
```

_exec_
```bash
node tagged.js
```

_output_
```javascript
Hello <b>world</b>!<b></b>
```

Tagged templates allow custom processing of template literals through a function prefix.

- Tagged templates are used in libraries like styled-components and GraphQL.
- The tag function receives an array of string parts and interpolated values.

**Best practices:**

- Use template literals instead of string concatenation.
- Keep expressions inside ${} simple; extract complex logic to variables.

**Common errors:**

- **Using single/double quotes instead of backticks for interpolation**: Template literals require backticks (`), not single (') or double ("") quotes.
- **Forgetting ${} around expressions**: Wrap expressions in ${} inside template literals.

### Operators

Comparison, logical, nullish coalescing, and optional chaining operators.

**Keywords:** operator, comparison, equality, ternary, nullish, optional chaining, spread

#### Comparison and logical operators

```javascript
// Equality
1 == '1'        // true (type coercion)
1 === '1'       // false (strict)
1 != '1'        // false
1 !== '1'       // true

// Logical
true && 'yes'   // "yes"
false || 'fallback' // "fallback"
null ?? 'default'   // "default" (nullish coalescing)

// Ternary
const age = 20;
const status = age >= 18 ? 'adult' : 'minor';
```

?? only checks null/undefined, unlike || which also catches 0, "", and false.

- Always use === and !== to avoid type coercion bugs.
- ?? is safer than || when 0 or empty string are valid values.

#### Optional chaining and nullish assignment

```javascript
const user = {
  name: 'Alice',
  address: { city: 'NYC' }
};

// Optional chaining
console.log(user?.address?.city);    // "NYC"
console.log(user?.phone?.number);    // undefined

// Optional chaining with methods
console.log(user.toString?.());      // "[object Object]"
console.log(user.nonExistent?.());   // undefined

// Nullish assignment
let a = null;
a ??= 'default';
console.log(a); // "default"

let b = 0;
b ??= 42;
console.log(b); // 0 (not null/undefined)
```

_exec_
```bash
node optional.js
```

_output_
```javascript
NYC
undefined
[object Object]
undefined
default
0
```

Optional chaining (?.) safely accesses nested properties without throwing if intermediate values are null/undefined.

- ?.() for optional method calls, ?.[] for optional bracket access.
- Combine with ?? for safe default values.

**Best practices:**

- Use ?? instead of || when 0 or empty string are valid values.
- Use optional chaining to safely access deeply nested objects.
- Prefer strict equality (===) over loose equality (==).

**Common errors:**

- **Using || for defaults when 0 or "" are valid values**: Use ?? (nullish coalescing) which only triggers for null/undefined.
- **Not using optional chaining on potentially null objects**: Use obj?.prop to avoid "Cannot read property of null/undefined" errors.

## Functions

Function declarations, expressions, arrow functions, and advanced patterns.

### Arrow Functions

Concise function syntax introduced in ES6.

**Keywords:** arrow, function, lambda, fat arrow, implicit return, this

#### Arrow function syntax

```javascript
// Basic arrow function
const add = (a, b) => a + b;

// Single parameter (no parens needed)
const double = x => x * 2;

// No parameters
const greet = () => 'Hello!';

// Multi-line body (needs braces and return)
const sum = (a, b) => {
  const result = a + b;
  return result;
};

console.log(add(2, 3));    // 5
console.log(double(4));    // 8
console.log(greet());      // "Hello!"
```

_exec_
```bash
node arrow.js
```

_output_
```javascript
5
8
Hello!
```

Arrow functions provide concise syntax. Single expressions are implicitly returned. Multi-line bodies need explicit return.

- Arrow functions do not have their own this - they inherit it from the enclosing scope.
- Cannot be used as constructors (no new keyword).

#### Returning objects

```javascript
// Wrap object literal in parentheses
const makeUser = (name, age) => ({ name, age });

console.log(makeUser('Alice', 30));
// { name: 'Alice', age: 30 }

// Common in array methods
const names = ['Alice', 'Bob'];
const users = names.map((name, i) => ({ id: i, name }));
console.log(users);
```

_exec_
```bash
node arrow-obj.js
```

_output_
```javascript
{ name: 'Alice', age: 30 }
[ { id: 0, name: 'Alice' }, { id: 1, name: 'Bob' } ]
```

To return an object literal from an arrow function, wrap it in parentheses to avoid ambiguity with block syntax.

- Without parentheses, {} is treated as a function body, not an object.
- This pattern is very common with .map(), .filter(), and .reduce().

**Best practices:**

- Use arrow functions for callbacks and short functions.
- Use traditional function declarations for methods that need their own this.
- Wrap object literal returns in parentheses.

**Common errors:**

- **Using arrow functions as object methods and losing this context**: Use regular function syntax for object methods that need this.
- **Forgetting parentheses when returning object literals**: Use () => ({ key: value }) to return objects.

### Default Parameters

Setting default values for function parameters.

**Keywords:** default, parameter, argument, fallback, optional

#### Default parameter values

```javascript
function greet(name = 'World', greeting = 'Hello') {
  return `${greeting}, ${name}!`;
}

console.log(greet());              // "Hello, World!"
console.log(greet('Alice'));        // "Hello, Alice!"
console.log(greet('Bob', 'Hi'));   // "Hi, Bob!"

// Defaults can use previous parameters
function createUser(name, role = 'user', id = Date.now()) {
  return { name, role, id };
}
```

_exec_
```bash
node defaults.js
```

_output_
```javascript
Hello, World!
Hello, Alice!
Hi, Bob!
```

Default parameters are used when arguments are undefined or not provided. They can reference earlier parameters.

- null does NOT trigger defaults, only undefined does.
- Default values are evaluated at call time, not definition time.

#### Rest parameters

```javascript
function sum(...numbers) {
  return numbers.reduce((total, n) => total + n, 0);
}

console.log(sum(1, 2, 3));     // 6
console.log(sum(10, 20));       // 30

function tag(name, ...attrs) {
  return `<${name} ${attrs.join(' ')}>`;
}

console.log(tag('div', 'class="box"', 'id="main"'));
```

_exec_
```bash
node rest.js
```

_output_
```javascript
6
30
<div class="box" id="main">
```

Rest parameters (...name) collect remaining arguments into a real array. Must be the last parameter.

- Rest parameters replace the old arguments object.
- Unlike arguments, rest parameters are a real Array.

**Best practices:**

- Use default parameters instead of checking for undefined manually.
- Use rest parameters instead of the arguments object.
- Put parameters with defaults at the end.

**Common errors:**

- **Expecting null to trigger default values**: Only undefined triggers defaults. Use ?? operator for null handling.
- **Putting rest parameter before other parameters**: Rest parameter must be last in the parameter list.

### Closures

Functions that capture variables from their enclosing scope.

**Keywords:** closure, scope, lexical, encapsulation, factory, private

#### Basic closure

```javascript
function createCounter() {
  let count = 0;
  return {
    increment: () => ++count,
    decrement: () => --count,
    getCount: () => count
  };
}

const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.decrement()); // 1
console.log(counter.getCount());  // 1
```

_exec_
```bash
node closure.js
```

_output_
```javascript
1
2
1
1
```

The inner functions close over the count variable, maintaining access to it even after createCounter returns.

- count is private and cannot be accessed directly from outside.
- Each call to createCounter creates a new independent scope.

#### Factory function with closure

```javascript
function multiplier(factor) {
  return (number) => number * factor;
}

const double = multiplier(2);
const triple = multiplier(3);

console.log(double(5));  // 10
console.log(triple(5));  // 15
console.log(double(10)); // 20
```

_exec_
```bash
node factory.js
```

_output_
```javascript
10
15
20
```

Each call to multiplier creates a new closure capturing its own factor value.

- Closures are the basis for many functional programming patterns.
- They enable partial application and currying.

**Best practices:**

- Use closures for data privacy and encapsulation.
- Be aware of memory implications when closures capture large scopes.
- Use factory functions to create specialized versions of functions.

**Common errors:**

- **Closures in loops capturing the same variable**: Use let (block-scoped) instead of var, or create a new scope with IIFE.
- **Memory leaks from closures holding references to large objects**: Set captured variables to null when no longer needed.

## Objects and Arrays

Object manipulation, destructuring, spread operator, and array methods.

### Destructuring

Extract values from objects and arrays into variables.

**Keywords:** destructuring, extract, unpack, object, array, rename, default

#### Object destructuring

```javascript
const user = { name: 'Alice', age: 30, city: 'NYC' };

// Basic destructuring
const { name, age } = user;
console.log(name, age); // "Alice" 30

// Rename variables
const { name: userName, age: userAge } = user;
console.log(userName); // "Alice"

// Default values
const { name: n, role = 'user' } = user;
console.log(role); // "user"

// Nested destructuring
const data = { a: { b: { c: 42 } } };
const { a: { b: { c } } } = data;
console.log(c); // 42
```

_exec_
```bash
node destruct.js
```

_output_
```javascript
Alice 30
Alice
user
42
```

Object destructuring extracts properties into variables. Supports renaming, defaults, and nesting.

- Destructuring works in function parameters too.
- Use rest pattern { a, ...rest } to collect remaining properties.

#### Array destructuring

```javascript
const colors = ['red', 'green', 'blue', 'yellow'];

// Basic
const [first, second] = colors;
console.log(first, second); // "red" "green"

// Skip elements
const [, , third] = colors;
console.log(third); // "blue"

// Rest pattern
const [head, ...tail] = colors;
console.log(tail); // ["green", "blue", "yellow"]

// Swap variables
let a = 1, b = 2;
[a, b] = [b, a];
console.log(a, b); // 2 1
```

_exec_
```bash
node array-destruct.js
```

_output_
```javascript
red green
blue
["green", "blue", "yellow"]
2 1
```

Array destructuring uses position to extract values. Supports skipping, rest patterns, and variable swapping.

- Array destructuring works with any iterable (strings, maps, sets).
- The swap pattern avoids needing a temporary variable.

**Best practices:**

- Use destructuring in function parameters for clarity.
- Provide default values for potentially missing properties.
- Keep destructuring patterns reasonably shallow for readability.

**Common errors:**

- **Destructuring null or undefined throws a TypeError**: Use default values or check for null/undefined before destructuring.
- **Deep nested destructuring becomes hard to read**: Destructure in multiple steps for deeply nested objects.

### Spread and Rest

The spread (...) operator for expanding and collecting elements.

**Keywords:** spread, rest, merge, clone, copy, expand, collect

#### Spread with arrays and objects

```javascript
// Array spread
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const merged = [...arr1, ...arr2];
console.log(merged); // [1, 2, 3, 4, 5, 6]

// Array clone
const clone = [...arr1];

// Object spread
const defaults = { theme: 'dark', lang: 'en' };
const userPrefs = { lang: 'fr', fontSize: 14 };
const config = { ...defaults, ...userPrefs };
console.log(config);
// { theme: 'dark', lang: 'fr', fontSize: 14 }
```

_exec_
```bash
node spread.js
```

_output_
```javascript
[1, 2, 3, 4, 5, 6]
{ theme: 'dark', lang: 'fr', fontSize: 14 }
```

Spread expands iterables into individual elements. For objects, later properties override earlier ones.

- Spread creates shallow copies, not deep clones.
- Object spread is commonly used for immutable state updates.

#### Rest in function parameters and destructuring

```javascript
// Rest in functions
function log(first, ...rest) {
  console.log('First:', first);
  console.log('Rest:', rest);
}
log('a', 'b', 'c');

// Rest in destructuring
const { a, ...others } = { a: 1, b: 2, c: 3 };
console.log(others); // { b: 2, c: 3 }
```

_exec_
```bash
node rest-spread.js
```

_output_
```javascript
First: a
Rest: ["b", "c"]
{ b: 2, c: 3 }
```

Rest collects multiple elements into an array or object. Used in function parameters and destructuring.

- Rest must be the last element in destructuring or parameter lists.
- Rest in objects omits the explicitly destructured keys.

**Best practices:**

- Use spread for immutable operations (clone then modify).
- Use rest parameters instead of the arguments object.
- Remember spread only does shallow copies.

**Common errors:**

- **Expecting spread to deep clone nested objects**: Use structuredClone() or a library for deep cloning.
- **Mutating the original when using spread on nested objects**: Spread nested objects too or use structuredClone().

### Array Methods

Essential array methods for transformation, filtering, and reduction.

**Keywords:** map, filter, reduce, find, some, every, forEach, flat, includes

#### Transform and filter

```javascript
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// map - transform each element
const doubled = numbers.map(n => n * 2);
console.log(doubled); // [2, 4, 6, ..., 20]

// filter - keep matching elements
const evens = numbers.filter(n => n % 2 === 0);
console.log(evens); // [2, 4, 6, 8, 10]

// find - first match
const found = numbers.find(n => n > 3);
console.log(found); // 4

// some / every
console.log(numbers.some(n => n > 5));  // true
console.log(numbers.every(n => n > 0)); // true

// includes
console.log(numbers.includes(5)); // true
```

_exec_
```bash
node array-methods.js
```

_output_
```javascript
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
[2, 4, 6, 8, 10]
4
true
true
true
```

map transforms, filter selects, find returns first match, some/every test conditions, includes checks membership.

- These methods do not mutate the original array.
- Chain methods for expressive data processing pipelines.

#### Reduce and flat

```javascript
const numbers = [1, 2, 3, 4, 5];

// reduce - accumulate to single value
const sum = numbers.reduce((acc, n) => acc + n, 0);
console.log(sum); // 15

// reduce - group by
const people = [
  { name: 'Alice', dept: 'eng' },
  { name: 'Bob', dept: 'eng' },
  { name: 'Carol', dept: 'hr' }
];
const byDept = people.reduce((groups, person) => {
  (groups[person.dept] ??= []).push(person);
  return groups;
}, {});
console.log(byDept);

// flat and flatMap
const nested = [[1, 2], [3, [4, 5]]];
console.log(nested.flat());    // [1, 2, 3, [4, 5]]
console.log(nested.flat(Infinity)); // [1, 2, 3, 4, 5]

const sentences = ['hello world', 'foo bar'];
const words = sentences.flatMap(s => s.split(' '));
console.log(words); // ["hello", "world", "foo", "bar"]
```

_exec_
```bash
node reduce.js
```

_output_
```javascript
15
{ eng: [{...}, {...}], hr: [{...}] }
[1, 2, 3, [4, 5]]
[1, 2, 3, 4, 5]
["hello", "world", "foo", "bar"]
```

reduce accumulates array values. flat flattens nested arrays. flatMap maps and flattens in one step.

- Always provide an initial value for reduce.
- Use Object.groupBy() (ES2024) instead of reduce for grouping when available.

**Best practices:**

- Prefer map/filter over forEach when building new arrays.
- Always provide an initial value for reduce.
- Chain methods for readable data pipelines.

**Common errors:**

- **Forgetting the initial value in reduce**: Always pass an initial value as the second argument to reduce.
- **Using forEach when map or filter is more appropriate**: Use map to transform, filter to select forEach should only be used for side effects.

### Object Methods

Key Object static methods for working with objects.

**Keywords:** Object.keys, Object.values, Object.entries, Object.assign, Object.freeze, computed property

#### Object inspection methods

```javascript
const user = { name: 'Alice', age: 30, city: 'NYC' };

console.log(Object.keys(user));
// ["name", "age", "city"]

console.log(Object.values(user));
// ["Alice", 30, "NYC"]

console.log(Object.entries(user));
// [["name","Alice"], ["age",30], ["city","NYC"]]

// Convert entries back to object
const filtered = Object.fromEntries(
  Object.entries(user).filter(([k, v]) => typeof v === 'string')
);
console.log(filtered); // { name: 'Alice', city: 'NYC' }
```

_exec_
```bash
node object-methods.js
```

_output_
```javascript
["name", "age", "city"]
["Alice", 30, "NYC"]
[["name","Alice"], ["age",30], ["city","NYC"]]
{ name: 'Alice', city: 'NYC' }
```

Object.keys/values/entries return arrays for iteration. Object.fromEntries converts entries back to an object.

- These methods only return own enumerable properties.
- Object.entries + fromEntries is great for object transformation.

#### Computed properties and shorthand

```javascript
// Property shorthand
const name = 'Alice';
const age = 30;
const user = { name, age };
console.log(user); // { name: 'Alice', age: 30 }

// Computed property names
const key = 'color';
const obj = { [key]: 'blue', [`${key}Code`]: '#00f' };
console.log(obj); // { color: 'blue', colorCode: '#00f' }

// Method shorthand
const calc = {
  value: 0,
  add(n) { this.value += n; return this; },
  subtract(n) { this.value -= n; return this; }
};
calc.add(5).subtract(2);
console.log(calc.value); // 3
```

_exec_
```bash
node computed.js
```

_output_
```javascript
{ name: 'Alice', age: 30 }
{ color: 'blue', colorCode: '#00f' }
3
```

Property shorthand, computed names, and method shorthand make object creation more concise.

- Computed properties can use any expression inside brackets.
- Method shorthand has the same this behavior as regular functions.

**Best practices:**

- Use property shorthand when variable names match property names.
- Use Object.freeze() for truly constant objects.
- Prefer Object.entries() for iterating objects with both key and value.

**Common errors:**

- **Assuming Object.freeze() is deep**: Object.freeze() is shallow. Use structuredClone + freeze or a library for deep freeze.
- **Forgetting that Object.keys() returns strings**: Numeric keys are converted to strings. Use Map if you need non-string keys.

## Async JavaScript

Promises, async/await, and asynchronous patterns.

### Promises

Creating and chaining promises for asynchronous operations.

**Keywords:** promise, then, catch, finally, resolve, reject, async

#### Creating and using promises

```javascript
// Creating a promise
const fetchData = new Promise((resolve, reject) => {
  setTimeout(() => {
    const success = true;
    if (success) {
      resolve({ id: 1, name: 'Alice' });
    } else {
      reject(new Error('Failed to fetch'));
    }
  }, 1000);
});

// Consuming with .then/.catch/.finally
fetchData
  .then(data => console.log('Data:', data))
  .catch(err => console.error('Error:', err))
  .finally(() => console.log('Done'));
```

_exec_
```bash
node promise.js
```

_output_
```javascript
Data: { id: 1, name: 'Alice' }
Done
```

Promises represent eventual completion or failure. Use .then() for success, .catch() for errors, .finally() for cleanup.

- Promises are always asynchronous, even if resolved immediately.
- .catch() catches any error in the preceding chain.

#### Promise combinators

```javascript
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = Promise.reject('error');

// all - waits for all (fails fast)
Promise.all([p1, p2])
  .then(console.log); // [1, 2]

// allSettled - waits for all regardless
Promise.allSettled([p1, p3])
  .then(console.log);
// [{status:'fulfilled',value:1}, {status:'rejected',reason:'error'}]

// race - first to settle
Promise.race([p1, p2])
  .then(console.log); // 1

// any - first to fulfill
Promise.any([p3, p1])
  .then(console.log); // 1
```

_exec_
```bash
node combinators.js
```

_output_
```javascript
[1, 2]
[{status:'fulfilled',value:1},{status:'rejected',reason:'error'}]
1
1
```

Promise.all fails on any rejection. allSettled waits for all. race returns the fastest. any returns first fulfillment.

- Use Promise.all for parallel independent async operations.
- Use Promise.allSettled when you need results regardless of individual failures.

**Best practices:**

- Always handle rejections with .catch() or try/catch.
- Use Promise.all for parallel independent async tasks.
- Use Promise.allSettled when all results matter regardless of success.

**Common errors:**

- **Unhandled promise rejection**: Always add .catch() at the end of promise chains.
- **Using Promise.all when one failure should not cancel others**: Use Promise.allSettled instead.

### Async/Await

Syntactic sugar over promises for cleaner asynchronous code.

**Keywords:** async, await, try, catch, asynchronous, sequential, parallel

#### Basic async/await

```javascript
async function fetchUser(id) {
  try {
    const response = await fetch(`/api/users/${id}`);
    if (!response.ok) throw new Error('Not found');
    const user = await response.json();
    return user;
  } catch (error) {
    console.error('Failed:', error.message);
    return null;
  }
}

// Arrow async function
const getUser = async (id) => {
  const res = await fetch(`/api/users/${id}`);
  return res.json();
};

// Top-level await (in modules)
const data = await fetchUser(1);
```

async functions always return promises. await pauses execution until the promise resolves. Use try/catch for error handling.

- Top-level await works in ES modules only.
- async/await is syntactic sugar over promises.

#### Sequential vs parallel execution

```javascript
// Sequential (slow - one after another)
async function sequential() {
  const user = await fetchUser(1);    // waits...
  const posts = await fetchPosts(1);  // then waits...
  return { user, posts };
}

// Parallel (fast - both at once)
async function parallel() {
  const [user, posts] = await Promise.all([
    fetchUser(1),
    fetchPosts(1)
  ]);
  return { user, posts };
}

// Parallel with error handling
async function parallelSafe() {
  const results = await Promise.allSettled([
    fetchUser(1),
    fetchPosts(1)
  ]);
  return results.map(r =>
    r.status === 'fulfilled' ? r.value : null
  );
}
```

Use Promise.all with await for parallel execution of independent async operations. Sequential await is appropriate when operations depend on each other.

- Parallel execution can be significantly faster for independent operations.
- Use for...of with await for sequential iteration over async operations.

**Best practices:**

- Use Promise.all for independent parallel operations.
- Always wrap await in try/catch for error handling.
- Avoid await in loops; prefer Promise.all with map.

**Common errors:**

- **Using await in a forEach loop (does not work as expected)**: Use for...of loop or Promise.all(array.map(async ...)) instead.
- **Sequential awaits for independent operations**: Use Promise.all to run independent operations in parallel.

## Classes and Modules

ES6 classes, inheritance, and module import/export syntax.

### Classes

ES6 class syntax for object-oriented programming.

**Keywords:** class, constructor, extends, super, static, getter, setter, private

#### Class declaration and inheritance

```javascript
class Animal {
  #name;  // private field

  constructor(name) {
    this.#name = name;
  }

  get name() { return this.#name; }
  set name(value) { this.#name = value; }

  speak() {
    return `${this.#name} makes a sound`;
  }

  static create(name) {
    return new Animal(name);
  }
}

class Dog extends Animal {
  #breed;

  constructor(name, breed) {
    super(name);
    this.#breed = breed;
  }

  speak() {
    return `${this.name} barks`;
  }

  info() {
    return `${this.name} is a ${this.#breed}`;
  }
}

const dog = new Dog('Rex', 'Labrador');
console.log(dog.speak());  // "Rex barks"
console.log(dog.info());   // "Rex is a Labrador"

const cat = Animal.create('Whiskers');
console.log(cat.speak());  // "Whiskers makes a sound"
```

_exec_
```bash
node classes.js
```

_output_
```javascript
Rex barks
Rex is a Labrador
Whiskers makes a sound
```

Classes support private fields (#), getters/setters, static methods, and inheritance via extends/super.

- Private fields (#) are truly private and not accessible outside the class.
- Static methods are called on the class, not instances.

#### Class with static and instance methods

```javascript
class EventEmitter {
  #listeners = new Map();

  on(event, callback) {
    if (!this.#listeners.has(event)) {
      this.#listeners.set(event, []);
    }
    this.#listeners.get(event).push(callback);
    return this;
  }

  emit(event, ...args) {
    const handlers = this.#listeners.get(event) ?? [];
    handlers.forEach(fn => fn(...args));
    return this;
  }

  off(event, callback) {
    const handlers = this.#listeners.get(event) ?? [];
    this.#listeners.set(event,
      handlers.filter(fn => fn !== callback)
    );
    return this;
  }
}

const emitter = new EventEmitter();
emitter
  .on('data', (msg) => console.log('Received:', msg))
  .emit('data', 'hello');
```

_exec_
```bash
node emitter.js
```

_output_
```javascript
Received: hello
```

A practical class example implementing a simple event emitter with method chaining (returning this).

- Method chaining is enabled by returning this.
- Private Map ensures listeners are encapsulated.

**Best practices:**

- Use private fields (#) for encapsulation.
- Prefer composition over deep inheritance hierarchies.
- Use static methods for utility functions related to the class.

**Common errors:**

- **Forgetting to call super() in derived class constructor**: Always call super() before using this in derived class constructors.
- **Using arrow functions for class methods that need to be overridden**: Use regular method syntax for overridable methods.

### Modules

ES6 module import/export syntax.

**Keywords:** import, export, default, named, module, dynamic, re-export

#### Named and default exports

```javascript
// math.js - Named exports
export const PI = 3.14159;
export function add(a, b) { return a + b; }
export function multiply(a, b) { return a * b; }

// utils.js - Default export
export default class Logger {
  log(msg) { console.log(`[LOG] ${msg}`); }
}

// app.js - Importing
import Logger from './utils.js';            // default
import { add, multiply, PI } from './math.js'; // named
import { add as sum } from './math.js';        // rename
import * as math from './math.js';             // namespace

console.log(math.add(2, 3));  // 5
console.log(sum(2, 3));        // 5
console.log(PI);               // 3.14159
```

Named exports allow multiple exports per module. Default exports are imported without braces. Use as to rename imports.

- A module can have one default export and many named exports.
- Use namespace import (* as) to import all named exports.

#### Dynamic imports and re-exports

```javascript
// Dynamic import (code splitting)
async function loadModule() {
  const { add } = await import('./math.js');
  console.log(add(2, 3)); // 5
}

// Conditional import
const lang = 'en';
const messages = await import(`./i18n/${lang}.js`);

// Re-exports (index.js barrel file)
export { add, multiply } from './math.js';
export { default as Logger } from './utils.js';
export * from './helpers.js'; // re-export all
```

Dynamic imports return a promise and enable code splitting. Re-exports create barrel files for cleaner imports.

- Dynamic imports are great for lazy loading in web apps.
- Barrel files (index.js) simplify imports from complex modules.

**Best practices:**

- Prefer named exports over default exports for better refactoring.
- Use dynamic imports for code splitting and lazy loading.
- Create barrel files (index.js) for module directories.

**Common errors:**

- **Mixing CommonJS require() with ES module import**: Use one module system consistently. Set "type":"module" in package.json for ESM.
- **Circular dependencies between modules**: Extract shared code into a separate module or restructure the dependency graph.

## Error Handling

Try/catch, custom errors, and error handling patterns.

### Try/Catch

Exception handling with try, catch, and finally blocks.

**Keywords:** try, catch, finally, throw, error, exception

#### Basic error handling

```javascript
try {
  const data = JSON.parse('invalid json');
} catch (error) {
  console.error('Parse error:', error.message);
} finally {
  console.log('Always runs');
}

// Throwing custom errors
function divide(a, b) {
  if (b === 0) throw new Error('Division by zero');
  return a / b;
}

try {
  console.log(divide(10, 0));
} catch (e) {
  console.error(e.message); // "Division by zero"
}
```

_exec_
```bash
node errors.js
```

_output_
```javascript
Parse error: Unexpected token i in JSON at position 0
Always runs
Division by zero
```

try/catch handles runtime errors. finally always executes. throw creates custom errors.

- finally runs whether or not an error occurred.
- Use specific error types for different error categories.

#### Custom error classes

```javascript
class ValidationError extends Error {
  constructor(field, message) {
    super(message);
    this.name = 'ValidationError';
    this.field = field;
  }
}

class NotFoundError extends Error {
  constructor(resource) {
    super(`${resource} not found`);
    this.name = 'NotFoundError';
    this.statusCode = 404;
  }
}

function validate(user) {
  if (!user.name) throw new ValidationError('name', 'Name required');
  if (!user.email) throw new ValidationError('email', 'Email required');
}

try {
  validate({ name: '' });
} catch (e) {
  if (e instanceof ValidationError) {
    console.log(`${e.field}: ${e.message}`);
  } else {
    throw e; // re-throw unknown errors
  }
}
```

_exec_
```bash
node custom-error.js
```

_output_
```javascript
name: Name required
```

Custom error classes extend Error for domain-specific error types. Use instanceof to handle specific error types.

- Always set this.name in custom errors for better debugging.
- Re-throw errors you cannot handle at the current level.

**Best practices:**

- Create custom error classes for different error categories.
- Always re-throw errors you do not handle.
- Use finally for cleanup operations.

**Common errors:**

- **Catching errors without re-throwing unknown ones**: Only catch specific error types, re-throw everything else.
- **Using try/catch for control flow**: Use conditional checks for expected conditions, try/catch for unexpected errors.

## Iterators and Generators

Iteration protocols, generators, and advanced iteration patterns.

### Iterators

The iteration protocol and creating custom iterables.

**Keywords:** iterator, iterable, Symbol.iterator, for..of, next, done, value

#### Custom iterable

```javascript
class Range {
  constructor(start, end) {
    this.start = start;
    this.end = end;
  }

  [Symbol.iterator]() {
    let current = this.start;
    const end = this.end;
    return {
      next() {
        if (current <= end) {
          return { value: current++, done: false };
        }
        return { done: true };
      }
    };
  }
}

const range = new Range(1, 5);
for (const n of range) {
  process.stdout.write(`${n} `);
}
// 1 2 3 4 5

console.log([...range]); // [1, 2, 3, 4, 5]
```

_exec_
```bash
node iterator.js
```

_output_
```javascript
1 2 3 4 5
[1, 2, 3, 4, 5]
```

Objects implementing [Symbol.iterator]() are iterable and work with for...of, spread, and destructuring.

- Built-in iterables include Array, String, Map, Set, and NodeList.
- Spread operator and destructuring consume iterables.

**Best practices:**

- Implement Symbol.iterator for custom collections.
- Use for...of instead of for...in for iterables.
- [object Object]

**Common errors:**

- **Using for...in instead of for...of for arrays**: for...in iterates keys/properties; for...of iterates values.
- **Forgetting to return { done: true } to end iteration**: Always return { done: true } when there are no more values.

### Generators

Generator functions that can pause and resume execution.

**Keywords:** generator, yield, function*, next, lazy, infinite, async generator

#### Generator basics

```javascript
function* count(start = 0) {
  let i = start;
  while (true) {
    yield i++;
  }
}

const counter = count(1);
console.log(counter.next().value); // 1
console.log(counter.next().value); // 2
console.log(counter.next().value); // 3

// Take first N from infinite generator
function* take(iterable, n) {
  let i = 0;
  for (const value of iterable) {
    if (i++ >= n) return;
    yield value;
  }
}

console.log([...take(count(10), 5)]);
// [10, 11, 12, 13, 14]
```

_exec_
```bash
node generator.js
```

_output_
```javascript
1
2
3
[10, 11, 12, 13, 14]
```

Generator functions (function*) use yield to pause execution. Each next() call resumes until the next yield.

- Generators are lazy; they only compute values on demand.
- Infinite generators are safe because they only produce values when consumed.

#### Async generators

```javascript
async function* fetchPages(url) {
  let page = 1;
  while (true) {
    const res = await fetch(`${url}?page=${page}`);
    const data = await res.json();
    if (data.length === 0) return;
    yield data;
    page++;
  }
}

// Consuming async generator
async function getAllPages() {
  const pages = [];
  for await (const page of fetchPages('/api/items')) {
    pages.push(...page);
    if (pages.length > 100) break;
  }
  return pages;
}
```

Async generators combine async/await with generator syntax. Use for await...of to consume them.

- Async generators are great for paginated API calls.
- for await...of works with any async iterable.

**Best practices:**

- Use generators for lazy evaluation of large or infinite sequences.
- Use async generators for streaming async data.
- Combine generators with helper functions like take, filter, map.

**Common errors:**

- **Forgetting the asterisk in function* declaration**: Generator functions require the * syntax: function* name() {}.
- **Not consuming the generator (calling it returns an iterator, not a value)**: Call .next() or use for...of to consume generator values.

## Modern Features

Recent JavaScript features including Map, Set, Proxy, and other ES2020+ additions.

### Map and Set

Map and Set collections for unique values and key-value pairs.

**Keywords:** Map, Set, WeakMap, WeakSet, collection, unique, key-value

#### Map usage

```javascript
const map = new Map();

// Any value can be a key
map.set('name', 'Alice');
map.set(42, 'the answer');
map.set(true, 'yes');

console.log(map.get('name'));  // "Alice"
console.log(map.has(42));      // true
console.log(map.size);         // 3

// Initialize from entries
const config = new Map([
  ['theme', 'dark'],
  ['lang', 'en'],
  ['debug', false]
]);

// Iteration
for (const [key, value] of config) {
  console.log(`${key}: ${value}`);
}

// Convert to/from object
const obj = Object.fromEntries(config);
const map2 = new Map(Object.entries(obj));
```

_exec_
```bash
node map.js
```

_output_
```javascript
Alice
true
3
theme: dark
lang: en
debug: false
```

Map allows any type as keys, maintains insertion order, and provides efficient key-value storage.

- Map is better than objects when keys are not strings.
- Use map.size instead of Object.keys(obj).length.

#### Set usage

```javascript
// Unique values
const set = new Set([1, 2, 3, 2, 1]);
console.log([...set]); // [1, 2, 3]

set.add(4);
set.delete(1);
console.log(set.has(2));  // true
console.log(set.size);    // 3

// Array deduplication
const arr = [1, 1, 2, 3, 3, 4];
const unique = [...new Set(arr)];
console.log(unique); // [1, 2, 3, 4]

// Set operations (ES2025+)
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
console.log([...a.intersection(b)]);  // [2, 3]
console.log([...a.union(b)]);          // [1, 2, 3, 4]
console.log([...a.difference(b)]);     // [1]
```

_exec_
```bash
node set.js
```

_output_
```javascript
[1, 2, 3]
true
3
[1, 2, 3, 4]
[2, 3]
[1, 2, 3, 4]
[1]
```

Set stores unique values of any type. Perfect for deduplication and membership testing.

- Set uses SameValueZero comparison (similar to ===).
- Set operations (union, intersection, difference) are available in modern engines.

**Best practices:**

- Use Set for collections of unique values.
- Use Map when keys are not strings or you need ordered key-value pairs.
- Prefer Map over plain objects for dynamic key-value storage.

**Common errors:**

- **Expecting Set to deduplicate objects by value**: Set uses reference equality for objects. Two identical objects are treated as different.
- **Using map[key] syntax instead of map.get(key)**: Map uses .get()/.set()/.has() methods, not bracket notation.

### Proxy and Reflect

Metaprogramming with Proxy and Reflect for intercepting operations.

**Keywords:** Proxy, Reflect, handler, trap, get, set, metaprogramming

#### Validation proxy

```javascript
const validator = {
  set(target, prop, value) {
    if (prop === 'age') {
      if (typeof value !== 'number') {
        throw new TypeError('Age must be a number');
      }
      if (value < 0 || value > 150) {
        throw new RangeError('Age must be 0-150');
      }
    }
    target[prop] = value;
    return true;
  },
  get(target, prop) {
    if (prop in target) return target[prop];
    throw new ReferenceError(`Property ${prop} not found`);
  }
};

const user = new Proxy({}, validator);
user.name = 'Alice';
user.age = 30;
console.log(user.name); // "Alice"

try { user.age = -5; } catch (e) {
  console.log(e.message); // "Age must be 0-150"
}
```

_exec_
```bash
node proxy.js
```

_output_
```javascript
Alice
Age must be 0-150
```

Proxy intercepts operations (get, set, delete, etc.) on objects. Handlers define traps for each operation.

- Proxies are used in Vue 3 reactivity and MobX state management.
- Use Reflect methods inside traps for default behavior.

#### Reactive proxy with onChange

```javascript
function reactive(target, onChange) {
  return new Proxy(target, {
    set(obj, prop, value) {
      const oldValue = obj[prop];
      obj[prop] = value;
      if (oldValue !== value) {
        onChange(prop, value, oldValue);
      }
      return true;
    },
    deleteProperty(obj, prop) {
      const value = obj[prop];
      delete obj[prop];
      onChange(prop, undefined, value);
      return true;
    }
  });
}

const state = reactive({ count: 0 }, (prop, newVal, oldVal) => {
  console.log(`${prop}: ${oldVal} -> ${newVal}`);
});

state.count = 1;  // "count: 0 -> 1"
state.count = 5;  // "count: 1 -> 5"
```

_exec_
```bash
node reactive.js
```

_output_
```javascript
count: 0 -> 1
count: 1 -> 5
```

A reactive proxy that calls onChange whenever properties are modified. This is the foundation of modern reactivity systems.

- This pattern is the basis of Vue 3 and similar frameworks.
- Add deep proxy wrapping for nested reactivity.

**Best practices:**

- Use Proxy for cross-cutting concerns like validation, logging, or reactivity.
- Use Reflect inside traps for consistent default behavior.
- Document proxy behavior clearly as it can be surprising.

**Common errors:**

- **Forgetting to return true from set trap**: The set trap must return true to indicate success in strict mode.
- **Infinite loops when proxy triggers itself**: Use a flag or WeakSet to track in-progress operations.

### Miscellaneous Modern Features

Useful modern JavaScript features and syntax.

**Keywords:** structuredClone, at, groupBy, using, with, globalThis

#### Useful modern additions

```javascript
// structuredClone - deep clone
const original = { a: { b: { c: 1 } }, d: [2, 3] };
const clone = structuredClone(original);
clone.a.b.c = 99;
console.log(original.a.b.c); // 1 (unchanged)

// Array.at() - negative indexing
const arr = [1, 2, 3, 4, 5];
console.log(arr.at(-1));  // 5
console.log(arr.at(-2));  // 4

// Object.groupBy (ES2024)
const people = [
  { name: 'Alice', age: 30 },
  { name: 'Bob', age: 25 },
  { name: 'Carol', age: 30 }
];
const byAge = Object.groupBy(people, p => p.age);
console.log(byAge[30]); // [Alice, Carol]

// String.replaceAll
const str = 'foo-bar-baz';
console.log(str.replaceAll('-', '_')); // "foo_bar_baz"
```

_exec_
```bash
node modern.js
```

_output_
```javascript
1
5
4
[{name:'Alice',age:30},{name:'Carol',age:30}]
foo_bar_baz
```

Modern JS includes structuredClone for deep copying, .at() for negative indexing, Object.groupBy for grouping, and more.

- structuredClone handles circular references but cannot clone functions.
- Object.groupBy replaces manual reduce-based grouping.

#### Pattern matching with switch(true) and logical assignment

```javascript
// Logical assignment operators
let a = null;
a ??= 'default';    // a = 'default' (null/undefined)
console.log(a);

let b = 0;
b ||= 42;           // b = 42 (falsy)
console.log(b);

let c = 1;
c &&= 2;            // c = 2 (truthy)
console.log(c);

// switch(true) pattern
const score = 85;
switch (true) {
  case score >= 90: console.log('A'); break;
  case score >= 80: console.log('B'); break;
  case score >= 70: console.log('C'); break;
  default: console.log('F');
}
```

_exec_
```bash
node logical.js
```

_output_
```javascript
default
42
2
B
```

Logical assignment operators combine logical operations with assignment. switch(true) enables range-based matching.

- ??= assigns only if null/undefined.
- ||= assigns if falsy (including 0, "", false).
- &&= assigns only if truthy.

**Best practices:**

- Use structuredClone instead of JSON.parse(JSON.stringify()) for deep cloning.
- Use .at(-1) instead of arr[arr.length - 1] for last element access.
- Use Object.groupBy when available instead of manual reduce.

**Common errors:**

- **Expecting structuredClone to clone functions or DOM nodes**: structuredClone works with structured-cloneable types only. Functions cannot be cloned.
- **Using ||= when 0 or empty string are valid values**: Use ??= which only checks for null/undefined.
