JavaScript Interview Cheatsheet

Key points from 112 JavaScript concepts in one scannable reference. Click any topic to explore the full interactive explanation.

Scope & Hoisting

  • Global scope: Variables accessible from anywhere in the code
  • Function scope: Variables declared with var inside a function
  • Block scope: Variables declared with let/const inside {}
  • Inner scopes can access outer scope variables (scope chain)
  • All declarations are hoisted to the top of their scope during compilation
  • var declarations are hoisted AND initialized with undefined
  • let and const declarations are hoisted but NOT initialized
  • Accessing let/const before declaration throws ReferenceError (TDZ)
  • Function declarations: fully hoisted (name + body)
  • Function expressions: follow variable hoisting rules
  • var function expressions: hoisted as undefined
  • let/const function expressions: TDZ error if called early
  • TDZ = time from scope start to variable declaration
  • Variables in TDZ exist but cannot be accessed
  • Accessing TDZ variable throws ReferenceError (not undefined)
  • typeof behaves differently in TDZ (throws vs returns "undefined")
  • Lexical = "where written" not "where called"
  • Scope chain: inner scope → outer scope → global
  • Variable lookup follows the scope chain until found
  • Scope is static - doesn't change based on how function is called
What is a Closure?Very High frequency
  • A closure is created when a function is defined inside another function
  • The inner function maintains access to outer scope variables
  • Variables are captured by reference, not by value
  • Closures exist even without explicit return (all inner functions are closures)
  • Data privacy: closures hide variables from external access
  • Function factories: create specialized functions with preset configuration
  • State preservation: maintain state in event handlers and callbacks
  • Memoization: cache expensive function results

Closures

  • A closure is created every time a function is created
  • Inner functions have access to outer function variables
  • The outer variables are captured by reference, not by value
  • Closures enable data privacy and stateful functions
  • var is function-scoped, not block-scoped
  • All closures in a var loop share the same variable reference
  • By the time callbacks run, the loop has finished (i is final value)
  • Fix 1: Use let instead of var (block-scoped, new binding each iteration)
  • Closures keep outer scope variables alive
  • Large objects captured in closures cannot be garbage collected
  • Event listeners with closures can hold DOM references
  • Remove event listeners when no longer needed
  • IIFE creates a new scope that encapsulates private variables
  • Return an object with public methods (closures)
  • Private variables are truly private - no external access
  • Before ES6 modules, this was the standard pattern
  • Partial application: fix some arguments, return function for rest
  • Currying: f(a,b,c) => f(a)(b)(c)
  • Both use closures to remember fixed arguments
  • Useful for creating specialized functions

Prototypes & OOP

  • Every object has [[Prototype]] (access via __proto__ or Object.getPrototypeOf)
  • __proto__ is the object it inherits from
  • .prototype is a property of constructor functions only
  • Property lookup walks up the prototype chain
  • Get: Walk up prototype chain until found or null
  • Set: Always creates/updates own property (never modifies prototype)
  • Own property shadows inherited property with same name
  • hasOwnProperty() checks only own properties
How instanceof WorksMedium frequency
  • obj instanceof Constructor checks prototype chain
  • Returns true if Constructor.prototype is in obj's chain
  • Walks up __proto__ links looking for prototype property
  • Does not work across iframes (different realms)
  • class is syntactic sugar over constructor functions and prototypes
  • constructor() method becomes the constructor function
  • Methods are added to Class.prototype
  • static methods become properties on the constructor
  • Subclass.prototype = Object.create(Superclass.prototype)
  • Call parent constructor: Superclass.call(this, args)
  • Fix constructor property after setting prototype
  • ES6 extends handles this automatically
  • Attacker injects properties via __proto__, constructor, or prototype
  • Affected operations: merge, clone, extend, deep assign
  • Can affect all objects if Object.prototype is polluted
  • Prevention: validate keys, use Object.create(null), freeze
  • Every object has a [[Prototype]] (accessible via __proto__ or Object.getPrototypeOf)
  • Property lookup walks up the prototype chain
  • Functions have a .prototype property used for constructor instances
  • Object.create() creates objects with a specific prototype
  • Private fields (#) are truly private — accessing them outside the class throws a SyntaxError
  • Static methods and properties belong to the class itself, not instances
  • Static initialization blocks (static { }) run once when the class is defined
  • Public class fields are declared at the top of the class body without let/const

Event Loop & Async

  • JavaScript is single-threaded (one call stack)
  • Web APIs handle async operations (setTimeout, fetch, etc.)
  • Task Queue (Macrotasks): setTimeout, setInterval, I/O
  • Microtask Queue: Promises, queueMicrotask, MutationObserver
  • Call stack is LIFO: Last In, First Out
  • Functions are pushed when called, popped when return
  • Stack overflow occurs with infinite recursion
  • Stack traces show the path of execution
  • JS Engine: Heap (memory) + Call Stack (execution)
  • Web APIs: setTimeout, fetch, DOM, etc. (provided by environment)
  • Engine executes code on call stack
  • Async operations are handed off to Web APIs
  • Macrotasks: setTimeout, setInterval, I/O, UI rendering
  • Callbacks queue here when Web APIs complete
  • Event loop runs ONE macrotask per iteration
  • Macrotasks yield between each other (browser can paint)
  • Microtasks: Promises, queueMicrotask, MutationObserver
  • Event loop drains ALL microtasks before next macrotask
  • Microtasks can queue more microtasks (drained recursively)
  • Always execute before next macrotask
  • 1. Execute one macrotask from task queue (if any)
  • 2. Drain ALL microtasks (execute until microtask queue empty)
  • 3. Render (if needed)
  • 4. Repeat
  • Infinite microtasks prevent macrotasks from running
  • Long sync operations block the entire thread
  • Use setTimeout(fn, 0) to yield control
  • Break heavy work into chunks
  • Callbacks (1995): Pass a function to run later - simple but leads to nesting
  • Callback Hell (2008): Deep nesting creates unreadable "pyramid of doom"
  • Promises (2012): Chainable .then() flattens code, unified error handling
  • Generators (2015): Pausable functions, paved the way for async/await
  • A callback is a function passed into another function as an argument
  • Callbacks can be synchronous (array methods) or asynchronous (setTimeout, events)
  • Callbacks allow for non-blocking code execution
  • Higher-order functions accept callbacks as parameters
  • Callback hell occurs with deeply nested asynchronous callbacks
  • Code becomes harder to read as indentation increases
  • Error handling becomes complex and scattered
  • Difficult to coordinate multiple async operations
Creating PromisesHigh frequency
  • new Promise(executor) creates a Promise object
  • Executor receives resolve and reject functions
  • Call resolve(value) to fulfill the Promise
  • Call reject(error) to reject the Promise
Promise ChainingHigh frequency
  • .then() always returns a new Promise
  • Return a value to pass it to the next .then()
  • Return a Promise to wait for it before continuing
  • Errors skip to the next .catch()
  • Use try/catch for async error handling
  • Rejected Promises become thrown exceptions in await
  • catch block receives the rejection reason
  • Can mix await and .catch() on same Promise
  • await runs sequentially by default
  • Use Promise.all() to run operations in parallel
  • Start operations before awaiting to run in parallel
  • Do NOT use await in a loop for independent operations
AbortControllerMedium frequency
  • AbortController creates a signal; calling controller.abort() triggers cancellation
  • Fetch accepts a signal option — aborting throws an AbortError that must be caught
  • AbortSignal.timeout(ms) creates a signal that auto-aborts after a duration
  • AbortSignal.any([signal1, signal2]) combines multiple signals — first abort wins

Array Methods

  • Mutating methods change the original array
  • push/pop work at end, shift/unshift work at beginning
  • splice can add, remove, or replace elements at any position
  • sort and reverse rearrange elements in place
  • forEach - executes function for each element, returns undefined
  • map - transforms each element, returns new array
  • filter - keeps elements passing test, returns new array
  • find - returns first element passing test, or undefined
  • reduce((accumulator, current) => newAccumulator, initialValue)
  • Can transform array into any type (number, object, array, etc.)
  • Always provide initial value to avoid bugs
  • Common patterns: sum, groupBy, flatten, pipe, count
  • indexOf/lastIndexOf - search by value, returns index or -1
  • includes - search by value, returns boolean
  • find/findIndex - search by predicate function
  • All use strict equality (===) for comparison
  • slice(start, end) - extracts portion, returns new array
  • concat(...arrays) - merges arrays, returns new array
  • flat(depth) - flattens nested arrays
  • flatMap(callback) - map + flat combined
  • sort() converts elements to strings by default!
  • Comparator: (a, b) => negative if a < b, positive if a > b, 0 if equal
  • Numeric sort: arr.sort((a, b) => a - b)
  • String sort: use localeCompare for proper Unicode
  • Immutable operations return new arrays
  • Spread operator [...arr] for shallow copies
  • ES2023: toSorted(), toReversed(), toSpliced(), with()
  • filter() for removing elements immutably

Modern JavaScript (ES6+)

  • Object: const {a, b} = obj
  • Array: const [x, y] = arr
  • Defaults: const {a = 1} = obj
  • Rename: const {a: newName} = obj
  • Arrays: [...arr1, ...arr2]
  • Objects: {...obj1, ...obj2}
  • Calls: fn(...args)
  • Shallow copies
Rest ParametersMedium frequency
  • function(...args)
  • Must be last parameter
  • Real array (unlike arguments)
  • Works with arrow functions
Template LiteralsMedium frequency
  • Interpolation: Hello ${name}
  • Multi-line support
  • Expression evaluation
  • Tagged templates
  • obj?.property
  • obj?.[key]
  • func?.()
  • Short-circuits on nullish
  • a ?? b for null/undefined
  • Unlike || (0, "" matter)
  • Cannot mix with && ||
  • Use when 0/"" valid
  • x ??= y (nullish)
  • x ||= y (falsy)
  • x &&= y (truthy)
  • Conditional assignment
SymbolsMedium frequency
  • Symbol() creates a guaranteed-unique value every time — no two symbols are ever equal
  • Symbol.for(key) uses a global registry — same key returns the same symbol across realms
  • Well-known symbols (Symbol.iterator, Symbol.toPrimitive, Symbol.hasInstance) customize language behavior
  • Symbols as property keys are not enumerable by for...in or Object.keys()
  • Spread (...) and Object.assign create shallow copies — only top-level properties are duplicated
  • Nested objects in shallow copies still share references with the original
  • JSON.parse(JSON.stringify()) loses undefined, functions, Date objects, Map, Set, and RegExp
  • structuredClone() (ES2022) handles nested objects, Date, Map, Set, RegExp, and circular references
  • A tag function receives (strings[], ...values) — strings are the static parts, values are the expressions
  • The strings array always has one more element than the values array
  • strings.raw gives unprocessed escape sequences (backslashes preserved)
  • String.raw is the built-in tag that returns raw strings without processing escapes

Other Topics

  • Created in 10 days for Netscape browser (1995)
  • Multi-paradigm: supports OOP, functional, and procedural styles
  • Dynamic typing: types are checked at runtime, not compile time
  • Prototype-based inheritance (not class-based like Java)
  • Code executes top-to-bottom, line-by-line (synchronous by default)
  • The engine maintains state: what variables exist, their current values
  • Comments are ignored - they are for humans only
  • Given the same input, code produces the same output (deterministic)
  • Programs manipulate values: numbers, text, true/false, collections
  • Values live in memory (think: boxes in a warehouse)
  • Variables are labels that point to memory locations
  • Reading a variable retrieves the value; writing changes what it points to
  • Expressions produce/evaluate to a value
  • Statements perform an action (don't produce a value)
  • Expressions can nest inside other expressions
  • Statements cannot go where expressions are expected
  • Professional developers spend most time reading existing code
  • Start by identifying inputs (parameters) and outputs (return value)
  • Find the "happy path" first - the normal successful flow
  • Variable and function names are documentation - read them carefully
  • Bugs are normal - expect them, don't fear them
  • Error messages are clues, not insults - read them carefully
  • The debugging cycle: Hypothesis → Test → Learn → Repeat
  • Isolate the problem: find the smallest code that reproduces it
  • var: function-scoped, can be redeclared, hoisted with undefined
  • let: block-scoped, cannot be redeclared, hoisted but in TDZ
  • const: block-scoped, cannot be reassigned, must be initialized
  • Use const by default, let when you need to reassign
  • 7 primitives: string, number, boolean, null, undefined, symbol, bigint
  • Objects: everything else (arrays, functions, dates, etc.)
  • typeof returns the type as a string
  • Primitives are copied by value, objects by reference
  • Arithmetic: + - * / % ** (exponentiation)
  • Comparison: == (loose) vs === (strict)
  • Logical: && (and) || (or) ! (not)
  • && returns first falsy or last value, || returns first truthy or last value
  • Function declarations are hoisted (can call before definition)
  • Function expressions are NOT hoisted
  • Arrow functions have shorter syntax and no own "this"
  • Functions can return values or undefined by default
  • if/else: most common, can chain with else if
  • switch: compare one value against many cases
  • Ternary: condition ? valueIfTrue : valueIfFalse
  • Falsy values: false, 0, "", null, undefined, NaN
  • for: when you know how many iterations
  • while: when you don't know how many iterations
  • for...of: iterate over array VALUES
  • for...in: iterate over object KEYS
  • Arrays are zero-indexed (first element is at index 0)
  • Arrays can hold mixed types
  • length property gives the count of elements
  • Mutating methods: push, pop, shift, unshift, splice
  • Objects store data as key-value pairs (properties)
  • Keys are strings (or Symbols), values can be anything
  • Dot notation: obj.key vs Bracket notation: obj["key"]
  • Objects are passed by reference, not copied
  • Variable declarations (var) are hoisted, but not their assignments
  • Function declarations are fully hoisted (name + body)
  • let and const are hoisted but stay in the "Temporal Dead Zone" until declared
  • Function expressions are NOT hoisted like function declarations
  • Seven primitives: string, number, boolean, null, undefined, symbol, bigint
  • typeof null is "object" (legacy quirk)
  • Loose equality (==) coerces types, strict (===) does not
  • + concatenates when either operand is a string
  • this is determined at call time, not definition time
  • Rule 1: new binding - this = new instance
  • Rule 2: Explicit binding (call/apply/bind) - this = specified object
  • Rule 3: Implicit binding (obj.method()) - this = object left of dot
  • A recursive function calls itself with a smaller/simpler input
  • Every recursion needs a BASE CASE to stop (or you get infinite recursion)
  • The call stack grows with each recursive call, then unwinds as functions return
  • Recursion can be converted to iteration (and vice versa)
  • Stack: stores primitives (numbers, booleans, strings) and function call frames
  • Heap: stores objects, arrays, and functions (dynamic allocation)
  • Variables hold references (pointers) to heap objects, not the objects themselves
  • Garbage Collection: V8 uses mark-and-sweep to free unreachable objects
  • Parser converts source code to Abstract Syntax Tree (AST)
  • Ignition: interpreter that generates bytecode for fast startup
  • TurboFan: JIT compiler that optimizes "hot" frequently-run code
  • Hidden Classes: V8 creates shapes for objects to optimize property access
  • Node.js event loop has 6 phases (not just micro/macro queues)
  • Timers phase: executes setTimeout/setInterval callbacks
  • Poll phase: retrieves new I/O events, executes I/O callbacks
  • Check phase: executes setImmediate callbacks
  • Streams process data in chunks (memory efficient)
  • Four types: Readable, Writable, Duplex, Transform
  • Buffers are fixed-size chunks of binary data
  • Backpressure: slow consumer signals fast producer to pause
  • HTML parsing builds the DOM (Document Object Model)
  • CSS parsing builds the CSSOM (CSS Object Model)
  • DOM + CSSOM = Render Tree (only visible elements)
  • Layout: calculates exact position and size of each element
  • Workers run in separate threads (true parallelism)
  • Main thread stays responsive during heavy computation
  • Communication via postMessage (data is copied, not shared)
  • Workers have no DOM access (no document, window, etc.)
  • Static HTML (1990s): Simple but no interactivity
  • CGI/PHP Era: Dynamic content but full page reloads
  • AJAX (2005): Partial updates without reload, but complex state
  • SPA Era (2010s): App-like UX but poor SEO & slow initial load
  • Global Scripts (1995): No modules, everything global, order-dependent
  • IIFE Pattern (2009): Closures for encapsulation, still manual ordering
  • CommonJS (2009): Node.js require/exports, synchronous, server-focused
  • AMD (2011): Async loading for browsers, verbose callback syntax
  • jQuery DOM (2006): State lived in the DOM, read/write directly
  • MVC/Backbone (2010): Separated models from views, event-driven
  • Two-Way Binding (2012): Angular 1.x magic, but hard to debug
  • Flux (2014): Unidirectional flow, actions → dispatcher → store
  • No Build (1995): Script tags, manual ordering, FTP deploy
  • Task Runners (2012): Grunt/Gulp automated concatenation and minification
  • Module Bundlers (2014): Webpack enabled import/export in browsers
  • Zero-Config (2017): CRA/Parcel hid complexity but were still slow
  • First parameter is always an error object (or null if no error)
  • Second parameter contains the successful result
  • Standard pattern in Node.js core modules (fs, http, etc.)
  • Always check if error exists before using result
  • A Promise represents a value that may be available now, later, or never
  • Promise states: pending → fulfilled OR rejected (immutable once settled)
  • Promise.all() fails fast: rejects on first rejection
  • Promise.race() returns first settled (fulfilled OR rejected)
  • .then(onFulfilled, onRejected) handles success and optionally error
  • .catch(onRejected) is shorthand for .then(null, onRejected)
  • .finally(onFinally) runs on both success and failure
  • These methods return new Promises (enabling chaining)
  • Promise.all([promises]) - waits for all, fails on first rejection
  • Promise.race([promises]) - returns first to settle (fulfilled OR rejected)
  • Promise.allSettled([promises]) - waits for all, never rejects
  • Promise.any([promises]) - returns first fulfilled, ignores rejections until all fail
  • async function always returns a Promise
  • await pauses execution until Promise settles
  • await can only be used inside async functions
  • Top-level await available in ES2022 (modules)
  • Currying transforms f(a, b, c) into f(a)(b)(c) - one arg at a time
  • Partial application fixes some arguments, returns function for the rest
  • pipe() flows left-to-right: pipe(f, g, h)(x) = h(g(f(x)))
  • compose() flows right-to-left: compose(f, g, h)(x) = f(g(h(x)))
  • Debounce: waits for pause in calls before executing (e.g., search input)
  • Throttle: executes at most once per interval (e.g., scroll handler)
  • Both use closures to persist timer state between calls
  • Leading edge: execute immediately on first call
  • Memoization trades memory for speed - cache results for repeated calls
  • Works best for pure functions (same input → same output)
  • Cache key is typically derived from function arguments
  • memoizeOne: caches only the last result (great for React)
  • try: code that might throw
  • catch: handles errors
  • finally: always runs
  • Error object properties
Native Error TypesMedium frequency
  • Error: base type
  • TypeError: wrong type
  • ReferenceError: undefined var
  • SyntaxError: invalid code
  • Extend Error class
  • Set name property
  • Capture stack trace
  • Custom properties
  • Arithmetic converts to numbers
  • String + anything = string
  • == coerces, === does not
  • Logical ops return values
  • [] + [] = ""
  • [] + {} = "[object Object]"
  • typeof null = "object"
  • NaN !== NaN
  • Strings are immutable — methods always return new strings
  • JS auto-boxes string primitives to String objects for method access
  • charAt(i) and bracket notation str[i] access individual characters
  • slice(start, end) extracts substrings (supports negative indices)
Equality ComparisonsVery High frequency
  • === (strict) compares without type coercion — prefer this by default
  • == (loose) performs type coercion before comparing, following complex rules
  • Object.is() is like === but handles NaN and +0/-0 correctly
  • NaN === NaN is false — use Object.is(NaN, NaN) or Number.isNaN()
JSONHigh frequency
  • JSON.parse(string) converts a JSON string into a JavaScript value
  • JSON.stringify(value) converts a JavaScript value into a JSON string
  • JSON cannot represent: functions, undefined, Symbol, circular references
  • replacer parameter in stringify controls which properties are included
  • for...in iterates over enumerable property keys (strings), including inherited ones
  • for...of iterates over iterable values (arrays, strings, maps, sets)
  • for...in on arrays gives string indices, not values — avoid this
  • for...in includes inherited properties — use hasOwnProperty to guard
  • typeof returns a string: "string", "number", "boolean", "undefined", "object", "function", "symbol", "bigint"
  • typeof null === "object" is a famous bug that can never be fixed
  • typeof for undeclared variables returns "undefined" (does not throw)
  • Array.isArray() is the only reliable way to check for arrays
  • && returns the first falsy value, or the last value if all are truthy
  • || returns the first truthy value, or the last value if all are falsy
  • Both operators return actual values, not necessarily booleans
  • Falsy values: false, 0, "", null, undefined, NaN (6 total)
Map & SetHigh frequency
  • Map keys can be any type (objects, functions, primitives) unlike object keys which are always strings/symbols
  • Map preserves insertion order and has a .size property
  • Set stores only unique values with O(1) add/has/delete
  • Both Map and Set are iterable with for...of, forEach, and spread
WeakMap & WeakSetMedium frequency
  • WeakMap keys and WeakSet values must be objects — no primitives
  • Entries are automatically garbage-collected when the key object has no other references
  • Not iterable — no for...of, no .keys(), no .values(), no .entries()
  • No .size property — the runtime cannot know the count since entries may be GC'd at any time
  • get/set syntax creates accessor properties that run code on read/write
  • Object.defineProperty controls writable, enumerable, and configurable flags
  • Data descriptors have value/writable; accessor descriptors have get/set — mutually exclusive
  • Object.getOwnPropertyDescriptor reveals the full descriptor of any property
  • Object.keys/values/entries return arrays of own enumerable string-keyed properties
  • Object.assign performs shallow copy — nested objects are still shared by reference
  • Object.freeze makes an object immutable (shallow) — nested objects are NOT frozen
  • Object.seal prevents adding/deleting properties but allows modifying existing values
Strict ModeMedium frequency
  • "use strict" at the top of a script or function body enables strict mode
  • ES modules and class bodies are always in strict mode — no directive needed
  • Standalone function this is undefined instead of the global object
  • Assigning to undeclared variables throws ReferenceError instead of creating globals
  • A HOF takes a function as an argument or returns a function (or both)
  • Function factories create specialized functions from general ones using closures
  • The middleware pattern chains HOFs where each can modify input, output, or control flow
  • Decorators wrap a function to add behavior without modifying the original
  • An iterator is an object with a next() method that returns { value, done }
  • An iterable is an object with a [Symbol.iterator]() method returning an iterator
  • Generator functions (function*) pause at each yield and resume when next() is called
  • Generators are lazy — they compute values on demand, not all at once
Regular ExpressionsMedium frequency
  • Two creation forms: /pattern/flags literal and new RegExp("pattern", "flags")
  • Flags: g (global), i (case-insensitive), m (multiline), s (dotAll), u (unicode)
  • Character classes: \d (digit), \w (word), \s (whitespace), . (any char)
  • Quantifiers: + (1+), * (0+), ? (0-1), {n,m} (range)
Proxy & ReflectMedium frequency
  • Proxy wraps a target object with a handler containing traps for operations
  • Common traps: get, set, has, deleteProperty, apply, construct
  • Reflect mirrors every Proxy trap and provides the default operation behavior
  • Always use Reflect inside traps to forward to the default behavior
Async IteratorsLow frequency
  • Symbol.asyncIterator defines the async iteration protocol on objects
  • Async generators (async function*) combine generators with async/await
  • for-await-of loop consumes async iterables, awaiting each yielded promise
  • Each next() call returns a Promise<{ value, done }>
  • addEventListener(type, handler, options) attaches event listeners to DOM nodes
  • Events propagate in 3 phases: capture (down), target, bubble (up)
  • event.target is the element that fired; event.currentTarget is the listener element
  • Event delegation: one parent listener handles events for all children via event.target
Fetch APIHigh frequency
  • fetch() returns a Promise<Response> — does NOT reject on HTTP errors (404, 500)
  • Always check response.ok or response.status before parsing the body
  • Body parsing methods: json(), text(), blob(), arrayBuffer(), formData()
  • Request options: method, headers, body, mode, credentials, signal
Web StorageMedium frequency
  • localStorage persists across sessions; sessionStorage clears when the tab closes
  • Both store strings only: use JSON.stringify/JSON.parse for objects and arrays
  • API: setItem(key, value), getItem(key), removeItem(key), clear(), key(index)
  • Storage limit is roughly 5MB per origin (varies by browser)