Back to Articles
26 min read

JavaScript Core: Functions, Data Structures, and Scope Mastery

Beyond simple syntax lies the power to engineer robust applications. This deep dive unpacks the versatility of JavaScript functions—from Arrow syntax to Currying—and explores the full depth of Object and Array manipulation. We conclude by demystifying the scope chain and closures, providing the mental model necessary to manage memory and state effectively.

Functions

Function declarations

Hoisted function definitions using the function keyword; available throughout their scope even before the line they're declared.

sayHello(); // Works due to hoisting function sayHello() { console.log("Hello!"); }

Function expressions

Functions assigned to variables; not hoisted, so they must be defined before use.

const greet = function(name) { return `Hello, ${name}`; }; console.log(greet("Alice"));

Anonymous functions

Functions without a name, commonly used as callbacks or immediately invoked; harder to debug due to missing stack trace names.

setTimeout(function() { console.log("Anonymous callback"); }, 1000);

Arrow functions

ES6 compact syntax with lexical this binding; cannot be used as constructors and don't have their own arguments object.

const add = (a, b) => a + b; const square = x => x * x; const greet = () => console.log("Hi");

Arrow function vs regular function

┌─────────────────────┬─────────────────────┬─────────────────────┐
│     Feature         │   Regular Function  │   Arrow Function    │
├─────────────────────┼─────────────────────┼─────────────────────┤
│ this binding        │ Dynamic (caller)    │ Lexical (parent)    │
│ arguments object    │ Yes                 │ No                  │
│ Constructor (new)   │ Yes                 │ No                  │
│ Hoisting            │ Yes (declarations)  │ No                  │
│ Implicit return     │ No                  │ Yes (single expr)   │
└─────────────────────┴─────────────────────┴─────────────────────┘

this keyword in different contexts

this refers to different objects based on invocation: global object (global), the object (method), new instance (constructor), or lexically inherited (arrow functions).

const obj = { name: "Alice", regular: function() { console.log(this.name); }, // "Alice" arrow: () => { console.log(this.name); } // undefined (global) };

Function parameters

Variables listed in function definition that receive values (arguments) when the function is called; JavaScript doesn't enforce parameter count.

function greet(name, age) { console.log(`${name} is ${age}`); } greet("Alice", 25); // Alice is 25 greet("Bob"); // Bob is undefined

Default parameters

ES6 feature allowing parameters to have fallback values when undefined is passed or argument is omitted.

function greet(name = "Guest", greeting = "Hello") { return `${greeting}, ${name}!`; } greet(); // "Hello, Guest!" greet("Alice"); // "Hello, Alice!"

Rest parameters

ES6 syntax using ... to collect remaining arguments into an array; must be the last parameter in the function definition.

function sum(...numbers) { return numbers.reduce((a, b) => a + b, 0); } sum(1, 2, 3, 4); // 10

arguments object

An array-like object (not a real array) containing all passed arguments; available in regular functions but not arrow functions.

function showArgs() { console.log(arguments.length); console.log(Array.from(arguments)); // Convert to real array } showArgs(1, 2, 3); // 3, [1, 2, 3]

Return statements

Exits function execution and optionally returns a value; functions without return (or empty return) yield undefined.

function divide(a, b) { if (b === 0) return null; // Early exit return a / b; }

Function scope

Variables declared inside a function with var are only accessible within that function; let/const add block-level scoping.

function example() { var x = 10; // Function-scoped if (true) { let y = 20; // Block-scoped } console.log(x); // 10 // console.log(y); // ReferenceError }

IIFE (Immediately Invoked Function Expression)

A function that executes immediately after creation, commonly used to create private scope and avoid polluting global namespace.

(function() { var private = "I'm private"; console.log(private); })(); // Also with arrow: (() => console.log("IIFE"))();

Callback functions

Functions passed as arguments to other functions, to be executed later (synchronously or asynchronously); foundation of async JavaScript.

function fetchData(callback) { setTimeout(() => { callback("Data received"); }, 1000); } fetchData(data => console.log(data));

Higher-order functions

Functions that take functions as arguments or return functions; enables functional programming patterns like map, filter, reduce.

const numbers = [1, 2, 3, 4, 5]; const doubled = numbers.map(n => n * 2); // [2, 4, 6, 8, 10] const evens = numbers.filter(n => n % 2 === 0); // [2, 4] const sum = numbers.reduce((a, b) => a + b, 0); // 15

Pure functions

Functions that always return the same output for the same input and have no side effects (don't modify external state); highly testable and predictable.

// Pure const add = (a, b) => a + b; // Impure (modifies external state) let total = 0; const addToTotal = (n) => total += n;

Function composition

Combining multiple functions where the output of one becomes the input of the next; creates complex operations from simple, reusable functions.

const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x); const add1 = x => x + 1; const double = x => x * 2; const add1ThenDouble = compose(double, add1); add1ThenDouble(5); // 12 → (5+1)*2

Currying

Transforming a function with multiple arguments into a sequence of functions, each taking a single argument; enables partial application.

const curry = (a) => (b) => (c) => a + b + c; curry(1)(2)(3); // 6 const add5 = curry(5); add5(3)(2); // 10

Partial application

Pre-filling some arguments of a function, returning a new function that takes the remaining arguments; different from currying which splits all arguments.

const greet = (greeting, name) => `${greeting}, ${name}!`; const sayHello = greet.bind(null, "Hello"); sayHello("Alice"); // "Hello, Alice!" // Using closure const partial = (fn, ...args) => (...rest) => fn(...args, ...rest);

Recursion

A function calling itself to solve problems by breaking them into smaller subproblems; requires a base case to prevent infinite loops.

function factorial(n) { if (n <= 1) return 1; // Base case return n * factorial(n - 1); // Recursive case } factorial(5); // 120 /* factorial(5) └─ 5 * factorial(4) └─ 4 * factorial(3) └─ 3 * factorial(2) └─ 2 * factorial(1) └─ 1 (base case) */

Tail call optimization

When a function's last action is calling another function, the engine can optimize by reusing the stack frame; only implemented in Safari currently.

// Not tail-optimized (multiplication after recursive call) const factorial = n => n <= 1 ? 1 : n * factorial(n - 1); // Tail-optimized (recursive call is final action) const factorialTCO = (n, acc = 1) => n <= 1 ? acc : factorialTCO(n - 1, n * acc);

Named function expressions

Function expressions with a name, useful for self-reference (recursion) and better stack traces in debugging.

const factorial = function fact(n) { if (n <= 1) return 1; return n * fact(n - 1); // Can reference itself by name }; console.log(factorial(5)); // 120 // console.log(fact); // ReferenceError (name not in outer scope)

╔══════════════════════════════════════════════════════════════════╗
║                    FUNCTIONS MENTAL MAP                          ║
╠══════════════════════════════════════════════════════════════════╣
║  Declaration Types    │   Invocation Patterns                    ║
║  ─────────────────    │   ───────────────────                    ║
║  • Declaration        │   • Direct call: fn()                    ║
║  • Expression         │   • Method: obj.fn()                     ║
║  • Arrow              │   • Constructor: new Fn()                ║
║  • IIFE               │   • call/apply/bind                      ║
╠═══════════════════════╪══════════════════════════════════════════╣
║  Parameters           │   Functional Patterns                    ║
║  ──────────           │   ───────────────────                    ║
║  • Default            │   • Higher-order                         ║
║  • Rest (...)         │   • Callbacks                            ║
║  • arguments obj      │   • Composition                          ║
║                       │   • Currying/Partial                     ║
╚═══════════════════════╧══════════════════════════════════════════╝

Objects

Object literals

Object literals are the simplest way to create objects in JavaScript using curly braces {} with key-value pairs separated by commas.

const person = { name: "John", age: 30, city: "NYC" };

Property access (dot vs bracket notation)

Dot notation is cleaner for known property names, while bracket notation allows dynamic keys and special characters.

const obj = { name: "Alice", "my-key": 42 }; console.log(obj.name); // "Alice" (dot) console.log(obj["my-key"]); // 42 (bracket - required for special chars) const key = "name"; console.log(obj[key]); // "Alice" (dynamic access)

Adding and deleting properties

Properties can be added dynamically by assignment and removed using the delete operator.

const obj = { a: 1 }; obj.b = 2; // Add property delete obj.a; // Remove property console.log(obj); // { b: 2 }

Object methods

Methods are functions stored as object properties, allowing objects to have behavior.

const calculator = { value: 0, add(n) { this.value += n; }, subtract(n) { this.value -= n; } }; calculator.add(5); // calculator.value = 5

this keyword

this refers to the object that is executing the current function; its value depends on how the function is called.

const user = { name: "Bob", greet() { console.log(`Hi, I'm ${this.name}`); } }; user.greet(); // "Hi, I'm Bob" // ⚠️ Arrow functions don't have their own 'this'

Object.keys, Object.values, Object.entries

These static methods return arrays of an object's enumerable property names, values, or key-value pairs respectively.

const obj = { a: 1, b: 2, c: 3 }; Object.keys(obj); // ["a", "b", "c"] Object.values(obj); // [1, 2, 3] Object.entries(obj); // [["a",1], ["b",2], ["c",3]]

Object.assign

Copies all enumerable own properties from one or more source objects to a target object, returning the modified target (shallow copy).

const target = { a: 1 }; const source = { b: 2, c: 3 }; Object.assign(target, source); // { a: 1, b: 2, c: 3 } // Common pattern: clone object const clone = Object.assign({}, original);

Object.create

Creates a new object with the specified prototype object and optional property descriptors.

const proto = { greet() { return `Hello, ${this.name}`; } }; const obj = Object.create(proto); obj.name = "Alice"; obj.greet(); // "Hello, Alice"
┌─────────────┐
│    obj      │──→ proto (has greet method)
│  name:"Alice"│
└─────────────┘

Object.freeze, Object.seal

freeze makes an object completely immutable (no add/delete/modify), while seal prevents adding/deleting but allows modifying existing properties.

const frozen = Object.freeze({ a: 1 }); frozen.a = 2; // Silently fails (or throws in strict mode) frozen.b = 3; // Silently fails const sealed = Object.seal({ a: 1 }); sealed.a = 2; // ✓ Works sealed.b = 3; // ✗ Fails

Object.preventExtensions

Prevents new properties from being added to an object, but existing properties can still be modified or deleted.

const obj = { a: 1 }; Object.preventExtensions(obj); obj.b = 2; // ✗ Fails obj.a = 99; // ✓ Works delete obj.a; // ✓ Works
Comparison:
                  Add   Delete   Modify
preventExtensions  ✗      ✓        ✓
seal               ✗      ✗        ✓
freeze             ✗      ✗        ✗

Property descriptors

Property descriptors define how a property behaves with attributes: value, writable, enumerable, and configurable.

// { value: any, writable: bool, enumerable: bool, configurable: bool } // OR for accessor properties: // { get: fn, set: fn, enumerable: bool, configurable: bool }

Object.defineProperty

Defines a new property or modifies an existing property with fine-grained control over property descriptors.

const obj = {}; Object.defineProperty(obj, 'secret', { value: 42, writable: false, enumerable: false, configurable: false }); console.log(obj.secret); // 42 obj.secret = 100; // Fails silently console.log(Object.keys(obj)); // [] (not enumerable)

Object.getOwnPropertyDescriptor

Returns the property descriptor for an own property of an object.

const obj = { name: "John" }; const desc = Object.getOwnPropertyDescriptor(obj, 'name'); // { value: "John", writable: true, enumerable: true, configurable: true }

Computed property names

Allows using expressions inside square brackets as property names when defining objects.

const prefix = "user"; const id = 123; const obj = { [prefix + "_" + id]: "data", [`${prefix}Name`]: "Alice" }; // { user_123: "data", userName: "Alice" }

Property shorthand

When variable names match property names, you can use shorthand syntax to avoid repetition.

const name = "Alice"; const age = 30; // Instead of: { name: name, age: age } const person = { name, age }; // { name: "Alice", age: 30 }

Method shorthand

Allows defining methods without the function keyword, providing cleaner syntax.

// Old way const obj1 = { sayHi: function() { return "Hi"; } }; // Shorthand const obj2 = { sayHi() { return "Hi"; } };

Getters and setters

Getters and setters are special methods that provide computed properties with read/write control.

const user = { firstName: "John", lastName: "Doe", get fullName() { return `${this.firstName} ${this.lastName}`; }, set fullName(value) { [this.firstName, this.lastName] = value.split(' '); } }; console.log(user.fullName); // "John Doe" user.fullName = "Jane Smith"; // Sets both names

Object destructuring

Extracts properties from objects into distinct variables with concise syntax.

const person = { name: "Alice", age: 30, city: "NYC" }; const { name, age } = person; // Basic const { name: n, age: a } = person; // Rename const { country = "USA" } = person; // Default value const { name, ...rest } = person; // Rest pattern

Spread operator with objects

Spreads an object's own enumerable properties into a new object, useful for shallow copying and merging.

const defaults = { theme: "dark", lang: "en" }; const userPrefs = { lang: "es" }; const settings = { ...defaults, ...userPrefs }; // { theme: "dark", lang: "es" } const clone = { ...original }; // Shallow clone

Object.is

Provides strict equality comparison that handles special cases like NaN and signed zeros correctly.

Object.is(25, 25); // true Object.is(NaN, NaN); // true (unlike ===) Object.is(0, -0); // false (unlike ===) // Comparison: NaN === NaN; // false 0 === -0; // true

Arrays

Array creation

Arrays can be created using literals, constructor, or static methods.

const arr1 = [1, 2, 3]; // Literal (preferred) const arr2 = new Array(3); // Creates array with 3 empty slots const arr3 = Array.of(1, 2, 3); // Creates [1, 2, 3] const arr4 = Array.from("abc"); // Creates ["a", "b", "c"]

Array literals

Array literals use square brackets with comma-separated values, the most common way to create arrays.

const numbers = [1, 2, 3, 4, 5]; const mixed = [1, "two", { three: 3 }, [4]]; const empty = [];

Array constructor

The Array constructor creates arrays but has quirky behavior with a single numeric argument.

const arr1 = new Array(5); // [empty × 5] - 5 empty slots! const arr2 = new Array(5, 6); // [5, 6] const arr3 = new Array("5"); // ["5"] // ⚠️ Prefer literals or Array.of() to avoid confusion

Array length

The length property returns the count of elements and can be modified to truncate or extend arrays.

const arr = [1, 2, 3, 4, 5]; console.log(arr.length); // 5 arr.length = 3; // Truncates to [1, 2, 3] arr.length = 5; // Extends to [1, 2, 3, empty × 2] arr.length = 0; // Clears array: []

Array indexing

Arrays use zero-based indexing; access elements with bracket notation, negative indices don't work natively.

const arr = ['a', 'b', 'c', 'd']; arr[0]; // 'a' arr[3]; // 'd' arr[-1]; // undefined (use arr.at(-1) for last element) arr.at(-1); // 'd' (ES2022)
Index:   0    1    2    3
       ┌────┬────┬────┬────┐
       │ 'a'│ 'b'│ 'c'│ 'd'│
       └────┴────┴────┴────┘
at(-1): -4   -3   -2   -1

Array methods (push, pop, shift, unshift)

These methods add/remove elements from array ends: push/pop for end, shift/unshift for beginning.

const arr = [2, 3]; arr.push(4); // [2, 3, 4] - returns new length arr.pop(); // [2, 3] - returns removed element (4) arr.unshift(1); // [1, 2, 3] - returns new length arr.shift(); // [2, 3] - returns removed element (1)
        unshift →  ┌───┬───┬───┐  ← push
                   │ 1 │ 2 │ 3 │
         shift ←   └───┴───┴───┘  → pop

splice, slice

splice modifies array in-place (remove/insert elements), slice returns a shallow copy of a portion without modifying original.

// splice(start, deleteCount, ...items) - MUTATES const arr = [1, 2, 3, 4, 5]; arr.splice(2, 1); // Removes 1 element at index 2 → [1, 2, 4, 5] arr.splice(2, 0, 'a'); // Inserts 'a' at index 2 → [1, 2, 'a', 4, 5] // slice(start, end) - NO MUTATION const arr2 = [1, 2, 3, 4, 5]; arr2.slice(1, 4); // [2, 3, 4] (end exclusive) arr2.slice(-2); // [4, 5]

concat

Returns a new array combining the original array with additional arrays or values.

const a = [1, 2]; const b = [3, 4]; const c = a.concat(b); // [1, 2, 3, 4] const d = a.concat(3, [4, 5]); // [1, 2, 3, 4, 5] // Original arrays unchanged

join, split

join converts array to string with separator; split (string method) converts string to array.

// join - Array → String const arr = ['a', 'b', 'c']; arr.join('-'); // "a-b-c" arr.join(''); // "abc" // split - String → Array const str = "a-b-c"; str.split('-'); // ["a", "b", "c"] str.split(''); // ["a", "-", "b", "-", "c"]

indexOf, lastIndexOf

Return the first/last index of an element, or -1 if not found; uses strict equality.

const arr = [1, 2, 3, 2, 1]; arr.indexOf(2); // 1 (first occurrence) arr.lastIndexOf(2); // 3 (last occurrence) arr.indexOf(99); // -1 (not found) arr.indexOf(2, 2); // 3 (search from index 2)

includes

Returns boolean indicating whether array contains a specified element; handles NaN correctly.

const arr = [1, 2, NaN, 4]; arr.includes(2); // true arr.includes(99); // false arr.includes(NaN); // true (indexOf would return -1) arr.includes(2, 2); // false (search from index 2)

find, findIndex, findLast, findLastIndex

Return the first/last element or index matching a predicate function, or undefined/-1 if not found.

const users = [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, { id: 3, name: 'Alice' } ]; users.find(u => u.name === 'Alice'); // { id: 1, name: 'Alice' } users.findIndex(u => u.name === 'Alice'); // 0 users.findLast(u => u.name === 'Alice'); // { id: 3, name: 'Alice' } users.findLastIndex(u => u.name === 'Alice'); // 2

filter

Creates a new array with all elements that pass the test implemented by the provided function.

const numbers = [1, 2, 3, 4, 5, 6]; const evens = numbers.filter(n => n % 2 === 0); // [2, 4, 6] const users = [{ name: 'Alice', active: true }, { name: 'Bob', active: false }]; const activeUsers = users.filter(u => u.active);

map

Creates a new array with the results of calling a function on every element.

const numbers = [1, 2, 3, 4]; const doubled = numbers.map(n => n * 2); // [2, 4, 6, 8] const names = [{name: 'Alice'}, {name: 'Bob'}]; const nameList = names.map(u => u.name); // ['Alice', 'Bob']

reduce, reduceRight

Executes a reducer function on each element, resulting in a single output value; reduceRight processes right-to-left.

const numbers = [1, 2, 3, 4]; // reduce(callback, initialValue) const sum = numbers.reduce((acc, cur) => acc + cur, 0); // 10 // Without initial value, first element is used const product = numbers.reduce((acc, cur) => acc * cur); // 24 // reduceRight - right to left ['a','b','c'].reduceRight((acc, cur) => acc + cur); // "cba"
reduce flow: [1, 2, 3, 4] → sum
  acc=0, cur=1 → 1
  acc=1, cur=2 → 3
  acc=3, cur=3 → 6
  acc=6, cur=4 → 10

forEach

Executes a function once for each array element; returns undefined and cannot be stopped (use for...of for breaking).

const arr = ['a', 'b', 'c']; arr.forEach((element, index, array) => { console.log(`${index}: ${element}`); }); // 0: a // 1: b // 2: c

some, every

some returns true if at least one element passes the test; every returns true only if all elements pass.

const numbers = [1, 2, 3, 4, 5]; numbers.some(n => n > 4); // true (5 > 4) numbers.every(n => n > 0); // true (all positive) numbers.every(n => n > 2); // false (1,2 fail) // Short-circuits when result is determined

sort

Sorts array in-place; converts elements to strings by default, so provide compare function for numbers.

const arr = [3, 1, 4, 1, 5]; // Default (string comparison) - WRONG for numbers! [10, 2, 1].sort(); // [1, 10, 2] ❌ // Correct numeric sort arr.sort((a, b) => a - b); // [1, 1, 3, 4, 5] ascending arr.sort((a, b) => b - a); // [5, 4, 3, 1, 1] descending // Strings ['banana', 'apple'].sort(); // ['apple', 'banana']

reverse

Reverses array in-place and returns the reference to the same array.

const arr = [1, 2, 3]; arr.reverse(); // [3, 2, 1] console.log(arr); // [3, 2, 1] - original mutated // Non-mutating reverse (ES2023) const reversed = arr.toReversed(); // New array

flat, flatMap

flat creates a new array with sub-array elements concatenated to specified depth; flatMap maps then flattens by one level.

// flat(depth = 1) const nested = [1, [2, [3, [4]]]]; nested.flat(); // [1, 2, [3, [4]]] nested.flat(2); // [1, 2, 3, [4]] nested.flat(Infinity); // [1, 2, 3, 4] // flatMap - map + flat(1) in one step const arr = [1, 2, 3]; arr.flatMap(x => [x, x * 2]); // [1, 2, 2, 4, 3, 6]

Array.from

Creates a new array from array-like or iterable objects, with optional map function.

Array.from('abc'); // ['a', 'b', 'c'] Array.from({ length: 3 }); // [undefined, undefined, undefined] Array.from({ length: 5 }, (_, i) => i); // [0, 1, 2, 3, 4] Array.from(document.querySelectorAll('div')); // NodeList to Array Array.from(new Set([1, 2, 2, 3])); // [1, 2, 3]

Array.of

Creates a new array with the given arguments as elements; fixes the single-number constructor issue.

Array.of(5); // [5] new Array(5); // [empty × 5] - confusing! Array.of(1, 2, 3); // [1, 2, 3] Array.of(undefined); // [undefined]

Array.isArray

Returns true if the value is an array; the reliable way to check for arrays.

Array.isArray([1, 2, 3]); // true Array.isArray('string'); // false Array.isArray({ length: 3 }); // false (array-like but not array) Array.isArray(new Array()); // true // Better than: typeof arr (returns 'object') // Better than: instanceof Array (fails across frames)

fill

Fills all or part of an array with a static value, modifying in-place.

const arr = [1, 2, 3, 4, 5]; arr.fill(0); // [0, 0, 0, 0, 0] arr.fill(9, 1, 3); // [0, 9, 9, 0, 0] (index 1 to 3) // Create array of same values new Array(5).fill(1); // [1, 1, 1, 1, 1] // ⚠️ Warning with objects new Array(3).fill({}); // Same object reference × 3!

copyWithin

Shallow copies part of an array to another location in the same array, without modifying its length.

const arr = [1, 2, 3, 4, 5]; // copyWithin(target, start, end) arr.copyWithin(0, 3); // [4, 5, 3, 4, 5] (copy from index 3 to index 0) arr.copyWithin(1, 3, 4); // [4, 4, 3, 4, 5] (copy index 3-4 to index 1)
[1, 2, 3, 4, 5].copyWithin(0, 3)
         ↑______↑
         copy [4,5] to position 0
Result: [4, 5, 3, 4, 5]

Array destructuring

Extracts values from arrays into distinct variables using position-based syntax.

const rgb = [255, 128, 64]; const [r, g, b] = rgb; // r=255, g=128, b=64 const [first, , third] = rgb; // Skip elements const [head, ...tail] = rgb; // head=255, tail=[128, 64] const [a, b, c = 0] = [1, 2]; // Default values: c=0 // Swap variables let x = 1, y = 2; [x, y] = [y, x]; // x=2, y=1

Spread operator with arrays

Expands array elements in places where multiple elements are expected.

const arr1 = [1, 2, 3]; const arr2 = [4, 5, 6]; const combined = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6] const copy = [...arr1]; // Shallow clone const withMore = [0, ...arr1, 4]; // [0, 1, 2, 3, 4] Math.max(...arr1); // 3 console.log(...arr1); // 1 2 3

Rest in destructuring

Collects remaining elements into a new array; must be the last element in the pattern.

const [first, second, ...rest] = [1, 2, 3, 4, 5]; // first = 1, second = 2, rest = [3, 4, 5] // In function parameters function sum(first, ...others) { return first + others.reduce((a, b) => a + b, 0); } sum(1, 2, 3, 4); // 10

Sparse arrays

Arrays with "holes" where indices have no values; created by setting length or deleting elements.

const sparse = [1, , , 4]; // Two holes sparse.length; // 4 sparse[1]; // undefined 1 in sparse; // false (hole) 0 in sparse; // true (has value) // Methods handle holes differently: sparse.forEach(x => console.log(x)); // 1, 4 (skips holes) sparse.map(x => x * 2); // [2, empty × 2, 8]
Index:    0      1      2      3
        ┌────┬──────┬──────┬────┐
        │ 1  │ hole │ hole │ 4  │
        └────┴──────┴──────┴────┘

Array-like objects

Objects with numeric indices and a length property but lacking array methods; common in DOM and arguments.

function example() { console.log(arguments); // { '0': 1, '1': 2, length: 2 } console.log(arguments.map); // undefined - not an array! // Convert to real array const arr1 = Array.from(arguments); const arr2 = [...arguments]; const arr3 = Array.prototype.slice.call(arguments); } const nodeList = document.querySelectorAll('div'); // Array-like

Typed arrays

Fixed-length arrays for raw binary data with specific numeric types; essential for WebGL, file processing, and performance-critical code.

// Create typed arrays const int8 = new Int8Array(4); // 4 signed 8-bit integers const float64 = new Float64Array([1.5, 2.5, 3.5]); const buffer = new ArrayBuffer(16); // 16 bytes raw memory const view = new Uint32Array(buffer); // View as 4 uint32s int8[0] = 127; int8[1] = 128; // Wraps to -128 (overflow)
Types available:
┌──────────────────┬─────────┬───────────┐
│ Type             │ Bytes   │ Range     │
├──────────────────┼─────────┼───────────┤
│ Int8Array        │ 1       │ -128..127 │
│ Uint8Array       │ 1       │ 0..255    │
│ Int16Array       │ 2       │ -32768... │
│ Uint16Array      │ 2       │ 0..65535  │
│ Int32Array       │ 4       │ ±2.1B     │
│ Float32Array     │ 4       │ IEEE 754  │
│ Float64Array     │ 8       │ IEEE 754  │
└──────────────────┴─────────┴───────────┘

Scope and Closures

Global Scope

Variables declared outside any function or block are in the global scope, accessible from anywhere in your code; in browsers, they become properties of the window object, which can lead to naming collisions and is generally considered bad practice.

var globalVar = "I'm global"; // Accessible everywhere console.log(window.globalVar); // "I'm global" (in browsers)

Function Scope

Variables declared with var inside a function are only accessible within that function, creating a private namespace that protects variables from external access.

function myFunc() { var secret = "hidden"; // Only accessible inside myFunc console.log(secret); // ✓ Works } console.log(secret); // ✗ ReferenceError

Block Scope

Variables declared with let and const are scoped to the nearest enclosing block (curly braces {}), unlike var which ignores block boundaries and is function-scoped.

if (true) { let blockScoped = "inside"; var functionScoped = "leaks out"; } console.log(functionScoped); // ✓ "leaks out" console.log(blockScoped); // ✗ ReferenceError

Lexical Scope

JavaScript uses lexical (static) scoping, meaning a function's scope is determined by where it's written in the source code, not where it's called from.

┌─────────────────────────────────┐
│ Global Scope                    │
│   ┌─────────────────────────┐   │
│   │ outer() Scope           │   │
│   │   ┌─────────────────┐   │   │
│   │   │ inner() Scope   │   │   │  ← inner can access outer & global
│   │   └─────────────────┘   │   │
│   └─────────────────────────┘   │
└─────────────────────────────────┘

Scope Chain

When resolving a variable, JavaScript looks up through nested scopes from innermost to outermost until it finds the variable or reaches global scope; this linked chain of scope objects is the scope chain.

const a = 1; function outer() { const b = 2; function inner() { const c = 3; console.log(a + b + c); // Walks chain: inner → outer → global } inner(); }

Closures

A closure is a function that remembers and accesses variables from its lexical scope even when executed outside that scope; the inner function "closes over" the outer variables.

function createCounter() { let count = 0; // Enclosed variable return function() { return ++count; // Still accesses count after createCounter returns }; } const counter = createCounter(); console.log(counter()); // 1 console.log(counter()); // 2

Closure Use Cases

Closures enable data privacy, function factories, partial application, event handlers with state, and callbacks that maintain context.

// Function factory const multiplyBy = (x) => (y) => x * y; const double = multiplyBy(2); const triple = multiplyBy(3); console.log(double(5)); // 10 console.log(triple(5)); // 15

Module Pattern

The module pattern uses an IIFE (Immediately Invoked Function Expression) with closures to create private state and expose only a public API, simulating encapsulation before ES6 modules existed.

const Calculator = (function() { let result = 0; // Private return { add: (x) => result += x, // Public API get: () => result }; })(); Calculator.add(5); console.log(Calculator.get()); // 5 console.log(Calculator.result); // undefined (private!)

Private Variables with Closures

Closures provide true privacy in JavaScript by keeping variables in an outer function's scope, inaccessible to external code but available to returned inner functions.

function BankAccount(initial) { let balance = initial; // Truly private return { deposit: (amt) => balance += amt, withdraw: (amt) => balance -= amt, getBalance: () => balance }; } const account = BankAccount(100); account.deposit(50); console.log(account.getBalance()); // 150 console.log(account.balance); // undefined

Memory Leaks with Closures

Closures can cause memory leaks when they unintentionally retain references to large objects or DOM elements that should be garbage collected, especially in long-lived callbacks or event handlers.

// ❌ Memory leak - largeData never freed function leaky() { const largeData = new Array(1000000); return function() { console.log(largeData.length); // Holds reference forever }; } // ✅ Fix - null out when done function fixed() { let largeData = new Array(1000000); const result = largeData.length; largeData = null; // Allow GC return function() { console.log(result); }; }