Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
Functions & Scope
Functions are first-class citizens in JavaScript. They can be assigned to variables, passed as arguments, and returned from other functions. This makes JavaScript incredibly flexible and powerful. “First-class citizen” means functions are treated like any other value. You can store a function in a variable the same way you store a number. You can pass a function into another function as an argument, the way you pass a string. You can return a function from a function, like returning a configured tool from a factory. This is the single most important idea in JavaScript — it unlocks closures, callbacks, higher-order functions, and most of the patterns you will use every day.1. Function Declarations vs Expressions
Function Declaration (Hoisted)
Function Expression (Not Hoisted)
Arrow Functions (ES6)
Concise syntax, and they don’t have their ownthis.
Function Types — Complete Comparison
| Feature | Declaration | Expression | Arrow |
|---|---|---|---|
| Syntax | function name() {} | const name = function() {} | const name = () => {} |
| Hoisted | Yes (entire function) | No (only variable, not value) | No (only variable, not value) |
Has own this | Yes | Yes | No (inherits from enclosing scope) |
Has arguments | Yes | Yes | No (use rest params ...args) |
Can be a constructor (new) | Yes | Yes | No (TypeError) |
Has prototype property | Yes | Yes | No |
| Implicit return | No | No | Yes (single expression only) |
Can be a generator (function*) | Yes | Yes | No |
| Best used for | Named functions, methods | Callbacks, IIFEs | Short callbacks, preserving this |
- Object/class methods: Use method shorthand (
greet() {}) or regular functions. They need their ownthis. - Callbacks for
.map,.filter,.then: Use arrow functions. Concise and nothisconfusion. - Event handlers in classes: Use arrow functions or
.bind()to preservethis. - Top-level named functions: Use declarations. Hoisting makes them available throughout the file.
- IIFEs: Either works, but arrow IIFEs (
(() => { ... })()) are more common in modern code.
2. Parameters & Arguments
Default Parameters (ES6)
Rest Parameters
Collect remaining arguments into an array.Spread Operator
Expand an array into individual arguments.Rest vs Spread — They Look Identical But Do Opposite Things
| Syntax | Name | Position | What it does |
|---|---|---|---|
function fn(...args) | Rest | Function parameter | Collects multiple arguments into one array |
fn(...array) | Spread | Function call | Expands one array into multiple arguments |
const [a, ...rest] = arr | Rest | Destructuring | Collects remaining elements into an array |
const copy = [...arr] | Spread | Array/Object literal | Expands elements into a new array/object |
3. Scope
Scope determines where variables are accessible. JavaScript has three types of scope.Global Scope
Variables declared outside any function or block.Function Scope
Variables declared inside a function (withvar, let, or const).
Block Scope (ES6)
Variables declared withlet or const inside {}.
Lexical Scope (Static Scope)
Functions are executed using the scope in which they were defined, not where they are called. Think of it like a postal address: a function’s “home address” is fixed at the time it is written. No matter where you call the function from later, it still looks up variables at its home address.4. Closures
A closure is a function that remembers its lexical scope even when executed outside that scope. This is one of the most powerful concepts in JavaScript. The backpack analogy: When a function is created, it packs a “backpack” of all the variables from its surrounding scope. Wherever that function goes — passed as a callback, returned from another function, stored in a variable — it carries that backpack with it. Even after the outer function has finished executing and its local variables would normally be garbage collected, the inner function still has access to them through its backpack. That backpack is the closure.Practical Use Cases
1. Data Privacy (Module Pattern)5. The Execution Context and Call Stack
Every time a function is called, JavaScript creates an Execution Context — a box that holds the function’s local variables, itsthis value, and a reference to the outer scope. These contexts are managed in a Call Stack (Last In, First Out).
Think of the call stack like a stack of plates at a buffet. Every time you call a function, a new plate goes on top. When that function returns, its plate is removed. You can only work with the plate on top. If you keep adding plates without removing any (infinite recursion), the stack crashes.
6. Higher-Order Functions
A higher-order function either takes a function as an argument or returns a function. They are central to functional programming.Functions as Arguments
Array Methods — When to Use Which
| Method | Returns | Mutates Original | Use When |
|---|---|---|---|
.map(fn) | New array (same length) | No | Transforming every element |
.filter(fn) | New array (subset) | No | Keeping elements that pass a test |
.reduce(fn, init) | Single value (any type) | No | Accumulating: sums, grouping, building objects |
.find(fn) | First match or undefined | No | Finding one element by condition |
.findIndex(fn) | Index or -1 | No | Finding the position of one element |
.some(fn) | boolean | No | ”Does at least one match?” (short-circuits) |
.every(fn) | boolean | No | ”Do all match?” (short-circuits) |
.forEach(fn) | undefined | No | Side effects only (logging, DOM updates) |
.sort(fn) | Same array (sorted) | Yes | Sorting in place (use .toSorted() for immutable) |
.includes(val) | boolean | No | ”Is this exact value in the array?” |
.flat(depth) | New array (flattened) | No | Removing nesting levels |
.flatMap(fn) | New array (mapped + flat(1)) | No | Map where each element expands to multiple items |
Chaining
7. IIFE (Immediately Invoked Function Expression)
A function that runs immediately after it is defined. Used to create a private scope. Before ES6 modules existed, IIFEs were the only way to avoid polluting the global scope — every major library (jQuery, Lodash, Backbone) was wrapped in one.Summary
- Function Types: Declarations are hoisted, expressions are not. Arrow functions are concise.
- Scope: Global → Function → Block. JavaScript uses lexical (static) scoping.
- Closures: Functions remember their lexical environment. Use for data privacy.
- Higher-Order Functions:
map,filter,reduceare your bread and butter. - Call Stack: LIFO structure for managing function execution.
Interview Deep-Dive
Explain closures to me like I am a senior engineer who has never written JavaScript. Then tell me how they can cause memory leaks.
Explain closures to me like I am a senior engineer who has never written JavaScript. Then tell me how they can cause memory leaks.
- A closure is a function bundled together with references to its surrounding lexical environment. When a function is defined inside another function, the inner function retains access to the outer function’s variables even after the outer function has returned and its execution context has been popped off the call stack. The runtime keeps those variables alive as long as the inner function exists.
- In implementation terms, when V8 creates a function, it attaches a hidden
[[Environment]]reference pointing to the lexical environment where the function was defined. That environment object holds the variable bindings. If the inner function referencescountfrom the outer scope,countis stored in a “context” object on the heap, not on the stack. The stack frame for the outer function is deallocated, but the heap-allocated context survives because the inner function still points to it. - Memory leak scenario: if you create a closure inside an event listener or a
setInterval, and you never remove the listener or clear the interval, the closure keeps its entire enclosing scope alive indefinitely. I have debugged a production leak where asetIntervalcallback inside a React component captured a largedataarray from the component’s scope. The component unmounted, but the interval was never cleared. Every 5 seconds, the callback ran, holding a reference to a 50MB dataset that could never be garbage collected. After a few hours, the browser tab consumed 2GB of memory. - The fix is always cleanup:
clearInterval,removeEventListener,AbortControllerfor fetch, or React’suseEffectcleanup return. The pattern is: if a closure outlives the scope that created it (which is the whole point of closures), you must have a plan for when to release it.
var causes all callbacks to print the same value, at the specification level.With var, the loop for (var i = 0; i < 3; i++) declares i in the function scope (not the block scope). There is exactly one i variable shared across all iterations. Each setTimeout callback creates a closure over that same i. By the time the callbacks execute (after the synchronous loop completes), i has been incremented to 3. All three callbacks read the same i, which is now 3. With let, the ECMAScript spec mandates that each iteration of a for loop creates a new lexical environment with a fresh binding for i. So each callback closes over a different i binding with the value at the time of that specific iteration: 0, 1, 2. The pre-ES6 workaround was an IIFE: (function(j) { setTimeout(() => console.log(j), 100); })(i) — the IIFE creates a new function scope for each iteration with its own j parameter.What is the difference between the call stack and the execution context? Walk me through exactly what happens when a function is called.
What is the difference between the call stack and the execution context? Walk me through exactly what happens when a function is called.
- The call stack is a LIFO data structure that tracks which function is currently executing. Each entry on the stack is an execution context. An execution context is the environment in which code is evaluated — it contains the variable environment (where
vardeclarations live), the lexical environment (wherelet/constdeclarations live), thethisbinding, and a reference to the outer lexical environment (for scope chain lookups). - When a function is called, the engine: (1) creates a new execution context for that function, (2) pushes it onto the call stack, (3) sets up the variable environment (hoists
vardeclarations and function declarations), (4) initializeslet/constbindings (but leaves them uninitialized — the TDZ), (5) determines thethisvalue based on how the function was called, (6) begins executing the function body line by line, (7) when the function returns (or throws), the execution context is popped off the stack. - The scope chain is built through lexical environments. Each execution context’s lexical environment has an “outer reference” pointing to the lexical environment of the enclosing scope. When the engine looks up a variable, it walks this chain: current environment, then outer, then outer’s outer, until it reaches the global environment. If not found, it throws a
ReferenceError. - In practice, you encounter the call stack most directly when debugging. The call stack in Chrome DevTools shows you the chain of execution contexts. A
RangeError: Maximum call stack size exceededmeans you have pushed more contexts than the engine’s limit (typically around 10,000-15,000 frames in V8, though it depends on the size of each frame).
factorial(100000) possible without blowing the stack. ES6 formally specifies TCO (called “Proper Tail Calls”), but in practice, only Safari/JavaScriptCore implements it. V8 (Chrome, Node.js) and SpiderMonkey (Firefox) chose not to implement it, citing concerns about developer tooling (stack traces become useless when frames are reused) and implicit performance cliffs. The practical implication: do not rely on TCO in JavaScript. If you need deep recursion, convert to an iterative approach with an explicit stack (an array you push to and pop from) or use trampolining (a pattern where a recursive function returns a thunk instead of calling itself, and a loop repeatedly invokes the thunks).When would you choose a regular function over an arrow function, and vice versa? Give me specific scenarios where using the wrong one causes bugs.
When would you choose a regular function over an arrow function, and vice versa? Give me specific scenarios where using the wrong one causes bugs.
- Arrow functions: use for callbacks, array method chains (
.map,.filter,.reduce), and any context where you want to inheritthisfrom the enclosing scope. They are concise, and the lexicalthisbinding eliminates the most common class ofthisbugs. - Regular functions: use for object methods (where you need
thisto be the object), constructors (arrow functions cannot be used withnew), and functions that need theargumentsobject (arrow functions do not have one). - Bug scenario 1 — arrow function as an object method:
const obj = { name: "Alice", greet: () => this.name }. Callingobj.greet()returnsundefined(or throws in strict mode) because the arrow function capturesthisfrom the enclosing scope (the module or global scope), not fromobj. The fix isgreet() { return this.name; }(method shorthand). - Bug scenario 2 — regular function as a callback inside a method:
person.listFriends = function() { this.friends.forEach(function(friend) { console.log(this.name + " knows " + friend); }); }. The innerfunctionhas its ownthis, which isundefinedin strict mode. The fix is either an arrow function (this.friends.forEach((friend) => {...})) or.bind(this). - Bug scenario 3 — arrow function with
prototype:const Foo = () => {}; Foo.prototypeisundefined. Arrow functions do not have aprototypeproperty and cannot be used as constructors.new Foo()throwsTypeError: Foo is not a constructor. - In practice, my rule is: arrow for lambdas, regular for anything that needs its own
thisorargumentsor will be called withnew.
=>* syntax in JavaScript. Arrow functions cannot be generators. If you try const gen = *() => { yield 1; }, you get a SyntaxError. You must use function* syntax: const gen = function*() { yield 1; } or function* gen() { yield 1; }. This is a deliberate language design choice — generators need their own execution context that can be suspended and resumed, which conflicts with the lightweight, lexically-bound nature of arrow functions.Explain higher-order functions. Then implement a basic version of Array.prototype.map from scratch.
Explain higher-order functions. Then implement a basic version of Array.prototype.map from scratch.
- A higher-order function is a function that either (a) takes one or more functions as arguments, or (b) returns a function. In JavaScript, this is possible because functions are first-class values — they can be stored in variables, passed as arguments, and returned from other functions, just like numbers or strings.
- Common higher-order functions:
Array.prototype.map(takes a transform function),Array.prototype.filter(takes a predicate function),setTimeout(takes a callback),addEventListener(takes a handler). Factory functions likedebounceandthrottleare higher-order because they accept a function and return a new function with modified behavior. - A basic
mapimplementation:
- The key details that separate a strong answer: (1)
callback.call(thisArg, ...)— the realmapaccepts an optionalthisArgas the second argument tomap, not just the callback. (2) Thei in thischeck handles sparse arrays —[1, , 3].map(x => x * 2)should produce[2, empty, 6], not[2, undefined, 6]. (3) The callback receives three arguments: the current element, the index, and the original array. (4)mapalways returns a new array of the same length — it does not mutate the original. - Performance nuance: in a hot path iterating over 100,000 elements,
.map().filter().reduce()creates two intermediate arrays. A singleforloop or a single.reduce()that combines all operations is more memory-efficient. But for typical use cases (hundreds to low thousands of elements), the clarity of chaining wins over the micro-optimization.
.map() and .forEach()? Can you use .map() everywhere you would use .forEach()?.map() returns a new array with the transformed values. .forEach() returns undefined and is used purely for side effects (logging, DOM manipulation, pushing to an external array). You can technically use .map() where you would use .forEach(), but it is a code smell: you are creating and discarding an array for no reason, which signals to other developers that the return value matters when it does not. Linting rules like no-unused-expressions or array-callback-return will flag this. The reverse is not true: you cannot use .forEach() where you need .map() because .forEach() does not return the transformed array. Also, .forEach() cannot be short-circuited (no break equivalent), while a for...of loop can.What is an IIFE and why were they necessary before ES6? Are there still valid use cases today?
What is an IIFE and why were they necessary before ES6? Are there still valid use cases today?
- An IIFE (Immediately Invoked Function Expression) is a function that is defined and executed in one step:
(function() { ... })(). The outer parentheses force JavaScript to treat thefunctionkeyword as an expression (not a declaration), and the trailing()immediately invoke it. - Before ES6, JavaScript had no module system and only function-level scope (no block scope). The only way to create a private scope and avoid polluting the global namespace was to wrap code in a function. Every major pre-ES6 library (jQuery, Lodash, Backbone, Angular 1.x) was wrapped in an IIFE. The revealing module pattern —
const module = (function() { let private = 0; return { getPrivate: () => private }; })()— was the standard way to achieve encapsulation. - Modern valid use cases still exist: (1) In scripts that are not ES modules (plain
<script>tags withouttype="module"), an IIFE still provides scope isolation. (2) When you need toawaitat the top level in an environment that does not support top-level await, you use an async IIFE:(async () => { const data = await fetch(...); })(). (3) In some build tool configurations and legacy codebases, IIFEs are still the output format for bundled code. - In a modern ES module environment with
let/const, IIFEs are rarely needed. Block scoping with{}andlet/constprovides the same isolation that IIFEs provided withvar. If you see an IIFE in modern code, it is usually either legacy or the async IIFE pattern.
(function(){})() and (function(){}()) — and does it matter?Both work identically. The first invokes the function outside the grouping parentheses; the second invokes it inside. Douglas Crockford (of “JavaScript: The Good Parts” fame) preferred the inner invocation style, arguing it makes the intent clearer by keeping the invocation visually inside the expression. In practice, it makes zero functional difference — the parser handles both the same way. Most modern code uses the first style. The truly important detail is the outer parentheses: without them, function(){}() is a syntax error because JavaScript sees function at the start of a statement and expects a declaration (which requires a name).