Web Development
JavaScript Questions
Comprehensive collection of the most frequently asked JavaScript interview questions covering fundamentals, ES6+, async programming, DOM manipulation, objects, arrays, functions, and advanced concepts. Each answer is concise, detailed, and interview-ready.
DOM & Events3
Event bubbling is the process where an event triggers on the target element and then propagates upwards through its parent elements in the DOM tree. When you click a child element, the event first fires on the child, then bubbles up to its parent, grandparent, and so on until it reaches the document root. You can stop this propagation using event.stopPropagation() method.
Event capturing (trickling) is the opposite of bubbling; events propagate from the document root down to the target element. The event first triggers on ancestors before reaching the target. Enable capturing by passing {capture: true} as the third argument to addEventListener(). Capturing happens before bubbling in the event flow: capture phase → target phase → bubble phase. It's useful for intercepting events before they reach their targets.
Event delegation attaches a single event listener to a parent element instead of multiple listeners on child elements, leveraging event bubbling. The parent catches events from children using event.target to identify the source. Benefits include better performance with fewer listeners, automatic handling of dynamically added elements, lower memory usage, and simpler code. Common for lists, tables, and dynamic content where child elements frequently change.
Functions11
These methods control the 'this' context in functions. call() invokes a function with a specified 'this' value and arguments passed individually. apply() is identical but accepts arguments as an array. bind() creates a new function with a permanently bound 'this' value and optional preset arguments, but doesn't immediately invoke it. They're essential for borrowing methods and maintaining proper context.
call() immediately invokes the function with the specified 'this' context and comma-separated arguments. bind() doesn't invoke the function immediately; instead, it returns a new function with the 'this' context permanently bound and optionally preset arguments. Use call() for immediate execution, bind() when you need to pass the function around or use it as an event handler while maintaining context.
Currying transforms a function accepting multiple arguments into a sequence of functions each taking a single argument. Instead of f(a,b,c), you get f(a)(b)(c). It enables partial application, creating specialized functions from generic ones. Currying improves code reusability, creates cleaner function composition, and helps with functional programming patterns. It's implemented by returning nested functions, each capturing one argument at a time.
A higher-order function either accepts functions as arguments or returns a function as its result. Examples include map, filter, reduce which take callback functions. Higher-order functions enable functional programming, code reusability, and abstraction. They're powerful for creating specialized functions, implementing decorators, and composing operations. Common patterns include forEach for iteration, memoization for caching, and debounce/throttle for performance optimization.
Arrow functions have concise syntax, lexically bind 'this' from enclosing scope, cannot be used as constructors, have no 'arguments' object, no prototype property, and cannot be used as generators. Regular functions have dynamic 'this', can be constructors, have 'arguments' object, and support all use cases. Use arrow functions for callbacks and methods not needing dynamic 'this'; use regular functions for constructors and methods requiring 'this' context.
Function declarations are hoisted completely, callable before definition, always named, and create variables in their scope. Function expressions are not hoisted (variable is), only callable after definition, can be anonymous or named, and are often assigned to variables. Declarations: function foo() {}; Expressions: const foo = function() {}. Use declarations for utility functions; expressions for callbacks, IIFEs, and conditional function creation.
IIFE is a function that executes immediately upon definition, written as (function(){})() or (() => {})(). It creates a private scope preventing variables from polluting global namespace, useful before ES6 modules. IIFEs are common in legacy code for encapsulation, creating closures, avoiding naming conflicts, and executing initialization code. Modern JavaScript often uses modules and block-scoped variables instead, but IIFEs remain useful for specific scoping needs.
Rest parameter (...variableName) collects remaining function arguments into an array, allowing functions to accept indefinite arguments. It must be the last parameter. Unlike the arguments object, rest parameters are true arrays with array methods. Use for variadic functions, flexible argument handling, and collecting extra arguments. Example: function sum(...numbers) gathers all arguments into numbers array. Rest parameters improve code clarity over the arguments object.
Parameters are variables listed in function definitions, acting as placeholders for values the function expects. Arguments are actual values passed to functions when calling them. Parameters define function signature; arguments provide concrete data. Example: function add(a, b) - a and b are parameters. When calling add(5, 3), 5 and 3 are arguments. Parameter count can differ from argument count, causing undefined or ignored values.
Default parameters assign default values to function parameters when no argument is provided or undefined is passed. Syntax: function greet(name = 'Guest'). They simplify functions by eliminating manual default value checks, improve code readability, and prevent undefined errors. Default parameters are evaluated at call time, can reference previous parameters, and use expressions. Introduced in ES6, they're now standard for handling optional function arguments.
Generator functions (function*) can pause execution and resume later, yielding multiple values over time. They return generator objects with next() method. Calling next() executes until yield, returning {value, done}. Generators enable lazy evaluation, infinite sequences, async iteration, and stateful iteration. Useful for handling large datasets without loading everything into memory, implementing iterators, and managing complex async flows before async/await. Syntax: function* gen() { yield value; }.
Arrays11
map() creates and returns a new array with transformed elements based on the callback function, leaving the original array unchanged. forEach() simply iterates through each element executing a callback but returns undefined and doesn't create a new array. Use map() when you need a transformed array, use forEach() for side effects like logging or updating external variables without returning values.
The reduce method returns a single accumulated value computed by iterating through an array and applying a reducer function. It takes an accumulator and current value, returning the updated accumulator for the next iteration. The final return value can be any type: number (sum), object (grouped data), array (filtered/transformed), or string (concatenated). An initial value parameter sets the starting accumulator value.
For loops imperatively iterate with explicit index control, modifying existing arrays, and can break/continue execution. They're faster for simple iterations but less declarative. Array.map() declaratively transforms arrays, returning a new array without mutating the original, and cannot break early. Map is more functional, chainable, and readable for transformations. Use for loops for performance-critical code with complex logic; use map for clear, functional array transformations.
Array.some() tests if at least one element passes the provided test function, returning true/false. It short-circuits, stopping at the first truthy result. Array.every() tests if all elements pass the test, returning true only if every element satisfies the condition and short-circuiting on first falsy result. Both are useful for validation: some() checks existence of qualifying items, every() verifies all items meet criteria.
Array.reduce() iterates through an array, applying a reducer function that combines elements into a single output value. The reducer takes an accumulator and current value, returning the updated accumulator. Common uses include summing numbers, flattening arrays, counting occurrences, grouping data, and building objects from arrays. It's powerful for transformations that aggregate or restructure data, replacing multiple operations with a single declarative expression.
Array.flat(depth) flattens nested arrays to specified depth, with Infinity flattening completely. Pre-ES2019, use recursive functions with reduce() and concat(), or spread operator with recursion. For simple cases, [].concat(...array) works for one level. Libraries like Lodash provide _.flattenDeep(). Choose based on depth needed and browser support. Flattening is useful for simplifying nested data structures and normalizing array operations.
slice(start, end) returns a new array containing selected elements without modifying the original array, using zero-based indices with end non-inclusive. splice(start, deleteCount, ...items) modifies the original array by removing elements and optionally inserting new ones, returning removed elements. slice for copying/extracting; splice for adding/removing. slice is non-mutating and safer; splice mutates arrays, useful for in-place modifications.
Array.filter() creates a new array with elements passing the test implemented by the provided callback function. It iterates through each element, calls the callback with element, index, and array, includes elements where callback returns truthy value. Original array remains unchanged. Common uses include removing items, searching arrays, data validation, and conditional selections. Filter is chainable with other array methods for complex transformations without loops.
Array.find() returns the first element satisfying the provided test function, or undefined if none found. Unlike filter which returns all matches, find stops at the first match, making it more efficient. The callback receives element, index, and array. Use find for locating single items in arrays, checking existence (with optional chaining or default values), and retrieving specific objects from collections based on conditions.
indexOf(value) searches for exact value matches using strict equality, returns first matching index or -1, limited to primitives. findIndex(callback) uses callback function for complex conditions, works with objects, returns first index where callback returns true or -1. Use indexOf for simple primitive searches; findIndex for objects or complex conditions. indexOf is faster for simple equality checks; findIndex offers more flexibility.
Array.includes(value, fromIndex) determines if array contains specified value, returning true/false. It uses SameValueZero algorithm (similar to ===) but correctly handles NaN. More readable than indexOf() !== -1 for existence checks. Optional fromIndex parameter specifies starting position. Use includes for simple existence checks of primitives. For objects or complex conditions, use find or some methods instead. Includes improves code clarity for boolean checks.
Core Concepts5
Hoisting is JavaScript's behavior of moving variable and function declarations to the top of their scope during compilation before code execution. Function declarations are fully hoisted with their definitions. Variables declared with var are hoisted but initialized as undefined. let and const are hoisted but remain in a temporal dead zone until their declaration is reached, causing ReferenceError if accessed early.
The 'this' keyword refers to the context object in which a function is executed, changing based on how the function is called. In methods, 'this' refers to the object owning the method. In regular functions, it's the global object (or undefined in strict mode). Arrow functions inherit 'this' from their enclosing scope. Constructor functions and explicit binding (call/apply/bind) can control 'this' value.
A closure is an inner function that has access to variables from its outer function's scope even after the outer function has finished executing. Closures remember their lexical environment, maintaining references to outer variables. They're useful for data privacy, creating function factories, implementing currying, and managing state in functional programming. Every function in JavaScript forms a closure over its surrounding scope.
Lexical scope (static scope) determines variable accessibility based on where functions and variables are physically written in code, not where they're called. Inner functions access variables from outer functions due to lexical scoping. JavaScript resolves variable references by searching the current scope, then moving outward through parent scopes until found or reaching global scope. Closures rely on lexical scoping to maintain access to outer variables.
Strict mode ('use strict') enables stricter parsing and error handling, preventing common mistakes and unsafe actions. It eliminates silent errors, throws errors for assignments to undeclared variables, disallows duplicate parameters, restricts 'this' in functions to undefined, prevents octal syntax, and makes eval safer. Enable globally or per-function. Benefits include easier debugging, better optimization, and preventing problematic JavaScript features that cause subtle bugs.
Asynchronous JavaScript7
A callback function is a function passed as an argument to another function and executed after the parent function completes its operation. Callbacks enable asynchronous programming, allowing code to run after tasks like API calls, file reads, or timers finish. They're essential for event handling and non-blocking operations, though excessive nesting can lead to callback hell, which Promises and async/await help resolve.
Callback hell, also called pyramid of doom, occurs when multiple nested callbacks create deeply indented, hard-to-read code. It happens when handling multiple sequential asynchronous operations, making code difficult to maintain, debug, and reason about. Modern JavaScript addresses this using Promises with .then() chaining or async/await syntax, which flatten the structure and improve readability while maintaining asynchronous behavior.
Callbacks are functions passed to handle asynchronous results but can lead to nested code. Promises represent future values with .then() for success and .catch() for errors, enabling cleaner chaining. Async/await is syntactic sugar over Promises, making asynchronous code look synchronous with try/catch for errors. Async/await is most readable, Promises improve upon callbacks, and callbacks are the foundational pattern.
Async/await is modern syntax for handling asynchronous operations built on Promises. The 'async' keyword makes a function return a Promise. The 'await' keyword pauses execution until the Promise resolves. Advantages include synchronous-looking code that's easier to read, simplified error handling with try/catch, better debugging with clear stack traces, and elimination of .then() chaining complexity while maintaining non-blocking behavior.
Promise.all() waits for all promises to resolve or rejects if any fails, useful for parallel operations needing all results. Promise.allSettled() waits for all to complete regardless of outcome, good for batch operations. Promise.race() resolves/rejects with the first settled promise, useful for timeouts. Promise.any() resolves with first successful promise, ideal for fallback strategies. Choose based on whether you need all results, any result, or fastest result.
Promise.all() accepts an array of promises and returns a single promise that resolves when all input promises resolve or rejects when any promise rejects. The resolved value is an array of results in the same order as input promises. It's ideal for running multiple independent asynchronous operations in parallel, like fetching data from multiple APIs simultaneously. If any promise fails, Promise.all() immediately rejects with that error.
Synchronous code executes sequentially, blocking execution until each operation completes before moving to the next, making programs wait for slow operations. Asynchronous code allows operations to run in the background without blocking, enabling other code to execute while waiting. Async operations use callbacks, Promises, or async/await. Use synchronous for simple sequential tasks; use asynchronous for I/O operations, API calls, and timers to maintain responsiveness.
Performance & Memory1
A memory leak occurs when memory is allocated but never released, causing applications to consume increasing memory over time. Common causes include forgotten timers, detached DOM nodes still referenced in code, closures holding unnecessary references, global variables accumulating data, and event listeners not properly removed. Memory leaks degrade performance, slow applications, and can crash browsers. Proper cleanup and reference management prevent them.
JavaScript Engine3
JavaScript engines like V8 parse code into an Abstract Syntax Tree, then compile it to bytecode or machine code. Execution uses a call stack for function calls and a heap for memory allocation. The event loop manages asynchronous operations, checking the call stack and task queues. Microtasks (Promises) run before macrotasks (setTimeout). Garbage collection automatically frees unused memory using mark-and-sweep algorithms.
The call stack tracks function execution in Last-In-First-Out order, with each function call creating a stack frame. When empty, the event loop checks task queues. Microtasks (Promises, queueMicrotask) run after current execution, before macrotasks. Macrotasks (setTimeout, setImmediate) run one per loop iteration. The event loop continuously checks: execute synchronous code, process all microtasks, run one macrotask, repeat. This enables non-blocking asynchronous operations.
The event loop is JavaScript's concurrency model enabling non-blocking asynchronous operations despite single-threaded execution. It continuously checks the call stack and task queues. When the stack is empty, it processes microtasks (Promises) completely, then one macrotask (setTimeout, I/O), then repeats. This mechanism allows JavaScript to handle multiple operations efficiently without blocking, making it suitable for I/O-intensive applications and user interfaces.
Objects9
Object literals use {} syntax for quick object creation. Object constructor uses 'new Object()'. Object.create() creates objects with specific prototypes for inheritance. Constructor functions use 'new' keyword with function templates. ES6 classes provide syntactic sugar over constructor functions. Factory functions return new objects without 'new' keyword. Each method has tradeoffs in syntax, inheritance support, and use cases like single instances versus multiple similar objects.
A prototype is an object from which other objects inherit properties and methods, forming JavaScript's inheritance mechanism. Every object has an internal [[Prototype]] link to another object. When accessing a property, JavaScript searches the object, then its prototype chain until found or reaching null. Prototypes enable efficient memory usage by sharing methods across instances and implementing inheritance without classes in traditional OOP sense.
Shallow cloning copies object's top-level properties; nested objects remain referenced. Methods include spread operator {...obj} and Object.assign(). Changes to nested objects affect both copies. Deep cloning recursively copies all nested objects, creating completely independent copies. Achieve deep cloning with JSON.parse(JSON.stringify()), structuredClone(), or libraries like Lodash. Use shallow cloning for simple objects, deep cloning when nested structures need full independence.
Object.freeze() makes objects completely immutable: cannot add, delete, or modify properties, and cannot change property descriptors. Object.seal() allows modifying existing property values but prevents adding or deleting properties. Both prevent extension. Freeze is strictest for constants; seal allows value updates while maintaining structure. Neither affects nested objects (shallow freeze/seal). Use freeze for complete immutability, seal for fixed structure with mutable values.
Deep equality compares all nested properties recursively to determine if two objects or arrays are structurally identical, unlike shallow equality which only checks references. Implementation requires recursively comparing primitive values, object keys, array lengths, and nested structures. Deep equality is crucial for comparing complex data structures, validating API responses, testing, and detecting actual data changes beyond reference changes. Libraries like Lodash provide optimized deep equality implementations.
For objects, use spread operator {...obj1, ...obj2} or Object.assign({}, obj1, obj2), with later properties overwriting earlier ones. For arrays, use spread [...arr1, ...arr2] or concat() method. For deep merging nested objects, use recursive functions or libraries like Lodash's _.merge(). Spread and Object.assign perform shallow merges. Choose based on whether you need shallow or deep merging and immutability requirements.
Prototypal inheritance is JavaScript's mechanism where objects inherit properties and methods from other objects through the prototype chain. Each object has an internal [[Prototype]] link to another object. When accessing a property, JavaScript searches the object, then its prototype, continuing up the chain until found or reaching null. This enables code reuse and method sharing without traditional class-based inheritance, fundamental to JavaScript's object-oriented capabilities.
Accessors define object properties that execute functions on access (getter) or assignment (setter). Define using get/set keywords in object literals or classes. Getters compute values dynamically without storing them; setters validate or transform values before storage. Benefits include computed properties, validation, logging property access, encapsulation, and backward-compatible API changes. Accessors make properties behave like methods while maintaining property syntax, enabling data hiding and validation.
Mutable objects can be changed after creation; their properties can be added, modified, or deleted. Objects and arrays are mutable by default. Immutable objects cannot be changed; modifications create new objects. Primitives are immutable. Benefits of immutability: predictable state, easier debugging, safe sharing, optimized change detection, and no unexpected mutations. Create immutable structures using Object.freeze(), spreading, or libraries like Immutable.js. Functional programming emphasizes immutability.
Design Patterns3
Design patterns are reusable solutions to common programming problems. Module pattern uses closures for encapsulation and privacy. Singleton ensures only one instance exists. Factory pattern creates objects without specifying exact classes. Observer pattern (pub/sub) manages event-driven communication. Prototype pattern clones objects efficiently. Implementation depends on requirements: use Singleton for database connections, Factory for object creation flexibility, and Observer for decoupled event handling.
An event emitter implements the observer pattern, allowing objects to emit and listen to events for decoupled communication. It provides methods like 'on' to register listeners, 'emit' to trigger events, and 'off' to remove listeners. Event emitters enable loosely coupled architecture, useful for building event-driven systems, handling user interactions, managing application state changes, and implementing pub/sub patterns for component communication.
Singleton ensures a class has only one instance and provides global access point to it. Implementation uses private constructor, static instance variable, and static getInstance() method. Use closures or ES6 modules for JavaScript singletons. Common applications include database connections, configuration managers, logging services, and caching. Singletons provide controlled access to shared resources but can make testing difficult and introduce global state.
Web APIs3
CORS (Cross-Origin Resource Sharing) is a security mechanism that controls how web pages from one domain can request resources from another domain. Browsers enforce the same-origin policy by default, blocking cross-origin requests. CORS headers like Access-Control-Allow-Origin enable controlled resource sharing. It's crucial for API security, preventing unauthorized data access while allowing legitimate cross-domain requests for modern web applications using separate frontend and backend servers.
AJAX (Asynchronous JavaScript and XML) is a technique for creating asynchronous web applications, updating parts of web pages without full reload. Despite the name, it commonly uses JSON instead of XML. AJAX uses XMLHttpRequest or modern fetch() API to send/receive data from servers asynchronously. It enables dynamic content updates, improved user experience, reduced server load, and responsive interfaces without page refreshes, fundamental to modern single-page applications.
XMLHttpRequest is older, event-driven with complex syntax requiring callbacks and event listeners. fetch() is modern, Promise-based with cleaner syntax, better error handling, and supports async/await. fetch() doesn't reject on HTTP errors (need to check response.ok), while XMLHttpRequest provides progress tracking. fetch() is more readable and chainable, now preferred in modern development. Both enable AJAX functionality but fetch() offers superior developer experience.
Data Structures4
Yes, JavaScript has a built-in Map object introduced in ES6 for storing key-value pairs. Unlike plain objects, Maps accept any data type as keys including objects and functions, maintain insertion order, have a size property, and are directly iterable. Maps provide better performance for frequent additions/deletions, avoid prototype chain issues, and offer methods like set, get, has, delete, and clear for efficient data management.
Maps accept any data type as keys (objects, functions, primitives); objects only accept strings/symbols. Maps maintain insertion order; object key order isn't guaranteed (though modern browsers preserve it). Maps have size property; objects need Object.keys().length. Maps are directly iterable with for...of; objects need Object.entries(). Maps perform better for frequent additions/deletions. Use Maps for dynamic key-value collections; objects for fixed structure data.
WeakMap and WeakSet only accept objects as keys/values and hold weak references allowing garbage collection, preventing memory leaks. They're not iterable and have no size property. Regular Map/Set accept any type, prevent garbage collection of referenced objects, are iterable, and have size property. Use WeakMap/WeakSet for temporary associations with objects that should be garbage collected when no longer needed elsewhere.
WeakMap stores object-keyed data allowing garbage collection when objects have no other references, preventing memory leaks. Use cases: private data storage, caching data associated with DOM elements, storing metadata about objects. WeakSet stores unique objects with automatic cleanup. Useful for tracking objects, marking objects without preventing collection, and implementing object tagging. Both help manage memory efficiently by not preventing garbage collection of referenced objects.
Object-Oriented Programming1
JavaScript supports OOP through prototypal inheritance, constructor functions, and ES6 classes. Use classes with constructor methods to define object templates, implement methods for behavior, and use extends for inheritance. Encapsulation is achieved through closures or private fields (#). Polymorphism works through method overriding. Abstraction uses interfaces or abstract patterns. Modern JavaScript OOP combines class syntax with functional programming paradigms for flexible, maintainable code.
Programming Paradigms1
Functional programming treats computation as evaluation of mathematical functions, avoiding state changes and mutable data. Key concepts include pure functions (no side effects), immutability, first-class functions, higher-order functions, function composition, and declarative code. JavaScript supports FP with map, filter, reduce, closures, and arrow functions. Benefits include predictable code, easier testing, better parallelization, and cleaner debugging through avoiding shared state.
Web Development1
Middleware are functions that execute during request-response cycles in web applications, sitting between request reception and final response. Each middleware can process requests, modify response objects, call next middleware, or end the request-response cycle. Common uses include authentication, logging, error handling, parsing request bodies, CORS handling, and data validation. Frameworks like Express.js extensively use middleware for modular, reusable request processing logic.
ES6 Features4
Destructuring extracts values from arrays or properties from objects into distinct variables using concise syntax. Array destructuring uses brackets: [a, b] = [1, 2]. Object destructuring uses braces: {name, age} = person. Benefits include cleaner code, default values, renaming properties, rest patterns, and nested destructuring. It's commonly used in function parameters, importing modules, swapping variables, and extracting multiple values from functions.
The spread operator (...) expands iterables (arrays, objects, strings) into individual elements. For arrays, it copies, merges, or passes elements as function arguments. For objects, it copies properties and merges objects (shallow copy). Use cases include array concatenation, cloning arrays/objects, converting iterables to arrays, passing array elements as function arguments, and creating new arrays/objects without mutation.
Template literals (template strings) use backticks (`) for string creation, enabling multi-line strings, string interpolation with ${expression}, and embedded expressions. They support tagged templates for custom string processing. Benefits include readable multi-line text without concatenation, expression embedding, cleaner string formatting, and creating domain-specific languages. They replace traditional concatenation and make string manipulation more elegant and maintainable.
ES6 (ES2015) introduced major JavaScript improvements: let/const for block-scoped variables, arrow functions, template literals, destructuring, spread/rest operators, Promises, classes, modules (import/export), default parameters, enhanced object literals, for...of loops, Map/Set data structures, Symbols, iterators/generators, and more. These features improve code readability, maintainability, and enable modern JavaScript development patterns like functional and object-oriented programming.
Data Types5
Undefined means a variable is declared but not assigned a value, or a function doesn't return anything, or accessing non-existent object properties. Null is an intentional assignment representing 'no value' or empty. Undefined is JavaScript's default, null is programmer-assigned. typeof undefined returns 'undefined', typeof null returns 'object' (historical bug). Use undefined for uninitialized variables, null for intentionally empty values.
JavaScript has seven primitive types: Number (integers and floats), String (text), Boolean (true/false), Undefined (declared but unassigned), Null (intentional absence), Symbol (unique identifiers), and BigInt (large integers). Non-primitive types include Object (collections of properties), Array (ordered lists), Function (executable code blocks), Date, RegExp, Map, and Set. Primitives are immutable and compared by value; objects are mutable and compared by reference.
NaN (Not-a-Number) is a special numeric value representing invalid or undefined mathematical operations like 0/0 or parseInt('text'). NaN is unique: NaN !== NaN returns true. Use Number.isNaN() for accurate checking (preferred), or isNaN() which coerces values first. typeof NaN returns 'number'. Common sources: failed type conversion, invalid math operations. Always use Number.isNaN() in modern code for precise NaN detection without coercion.
Symbol is a primitive data type creating unique, immutable identifiers primarily used as object property keys to avoid naming conflicts. Symbol('desc') creates unique symbols even with identical descriptions. Use cases include defining non-enumerable properties, creating private-like object properties, implementing well-known symbols (Symbol.iterator), and metadata keys in libraries. Symbols aren't automatically stringified, provide better encapsulation than strings, and enable meta-programming capabilities.
BigInt is a primitive type for representing integers larger than Number.MAX_SAFE_INTEGER (2^53 - 1) with arbitrary precision. Create using BigInt() function or 'n' suffix: 9007199254740991n. Cannot mix with regular numbers in operations without explicit conversion. Useful for cryptography, timestamps, large integers in scientific calculations, and financial precision. BigInt operations are slower than Number. Modern feature enabling accurate arbitrary-precision integer arithmetic in JavaScript.
Node.js Architecture1
Node.js uses a single-threaded event loop to simplify asynchronous programming and avoid multi-threading complexity like race conditions, deadlocks, and synchronization overhead. The event-driven, non-blocking I/O model efficiently handles thousands of concurrent connections without thread context switching costs. While JavaScript execution is single-threaded, Node.js uses libuv's thread pool for I/O operations, combining simplicity with performance for I/O-bound applications.
Modules1
require() is CommonJS syntax for Node.js, synchronously loading modules at runtime, returning exported objects. import is ES6 module syntax, statically analyzed at parse time, enabling tree-shaking and better optimization. import supports named exports, default exports, and import * as syntax. require is dynamic and can be conditional; import statements must be top-level. Modern JavaScript prefers import for better tooling support and standard compliance.
Variables4
const creates immutable variable bindings, not immutable values. For objects, const prevents reassigning the variable to a new object but allows modifying object properties. You can add, delete, or change properties of const objects. The reference is constant, not the content. To make object contents immutable, use Object.freeze(). This distinction prevents accidental variable reassignment while allowing necessary object mutations.
Yes, you can add, remove, or modify elements in const arrays using methods like push, pop, splice, and bracket notation. const prevents reassigning the array variable itself, not mutating the array contents. The array reference is constant, not its elements. To prevent modifications, use Object.freeze() which makes the array immutable. This behavior allows necessary array operations while preventing accidental variable reassignment.
var is function-scoped, hoisted with undefined initialization, and allows redeclaration. let and const are block-scoped, hoisted but in temporal dead zone, and prevent redeclaration. let allows reassignment, const doesn't (but allows object/array mutation). Best practice: use const by default for immutability, let when reassignment needed, avoid var due to scoping issues and hoisting quirks that can cause bugs in modern JavaScript development.
The Temporal Dead Zone (TDZ) is the period between entering scope and variable declaration for let/const variables. Accessing variables in TDZ throws ReferenceError. TDZ starts at scope beginning, ends at declaration line. Unlike var which hoists with undefined, let/const hoist but remain uninitialized in TDZ. This prevents usage before declaration, catching potential bugs. TDZ enforces better coding practices by requiring variable declaration before use.
Operators5
Double equals (==) performs type coercion before comparison, converting operands to the same type, leading to unexpected results like 0 == false being true. Triple equals (===) checks strict equality without type coercion, comparing both value and type. '5' == 5 is true, but '5' === 5 is false. Best practice: always use === to avoid implicit type conversion bugs unless you specifically need coercion.
typeof operator returns a string indicating the operand's type. Returns: 'undefined', 'boolean', 'number', 'string', 'symbol', 'bigint', 'function', or 'object'. Notable quirks: typeof null returns 'object' (historical bug), typeof array returns 'object', typeof NaN returns 'number'. Use Array.isArray() for arrays, obj === null for null checks. typeof is useful for runtime type checking but has limitations requiring additional checks for accurate type detection.
instanceof operator tests whether an object's prototype chain contains a constructor's prototype property, checking if object is an instance of a class/constructor. Syntax: object instanceof Constructor. Returns true/false. Works with built-in types (Array, Date) and custom classes. Limitations: doesn't work across iframe boundaries, checks prototype chain not direct constructor. Use for type checking objects when typeof isn't sufficient, especially with custom classes and inheritance.
The ternary operator (condition ? exprIfTrue : exprIfFalse) is JavaScript's only three-operand operator, providing concise conditional expressions. It evaluates condition, returns first expression if truthy, second if falsy. Useful for conditional assignments, inline conditions in JSX, and short conditional logic. Can be nested but becomes less readable. Alternative to if-else for simple cases. More compact than if statements but prioritize readability over brevity for complex logic.
Beyond &&, ||, !, JavaScript has nullish coalescing (??) for null/undefined checks, optional chaining (?.) for safe property access, and logical assignment operators: &&= (assign if truthy), ||= (assign if falsy), ??= (assign if null/undefined). These operators enable concise conditional logic, reduce code verbosity, and provide targeted handling of different value conditions. They're essential for modern JavaScript's expressive, declarative programming style.
Performance3
Debouncing delays function execution until after a specified time has passed since the last invocation, ensuring the function runs only after user activity stops. It's implemented with setTimeout, clearing previous timers on each invocation. Use cases include search input (wait for typing pause), window resize handlers, and scroll events. Debouncing prevents excessive function calls, improving performance by executing once after rapid repeated events rather than during them.
Throttling ensures a function executes at most once within a specified time interval, regardless of how many times it's triggered. Unlike debouncing which waits for silence, throttling runs at fixed intervals. Implement using a flag or timestamp checking. Use cases include scroll handlers, mouse move events, and window resizing where you need regular updates but not on every event. Throttling balances responsiveness with performance.
Memoization is an optimization technique caching function results based on inputs, returning cached values for repeated calls with same arguments instead of recomputing. Implement using closures with a cache object storing arguments as keys and results as values. Benefits include improved performance for expensive pure functions, reduced computation time, and better application responsiveness. Commonly used for recursive functions, API calls, and complex calculations.
DOM Manipulation3
getElementsByClassName() returns a live HTMLCollection of elements matching specified class names. It accepts space-separated class names for multiple classes. The collection automatically updates when DOM changes. It's faster than querySelectorAll for simple class selections but less flexible. Modern code often prefers querySelectorAll('.className') for static collections and CSS selector flexibility, though getElementsByClassName is more performant for basic class-based selections.
The DOM is a programming interface representing HTML/XML documents as tree structures where each node is an object representing document parts. JavaScript uses the DOM to access, manipulate, add, or delete HTML elements and attributes dynamically. The DOM bridges web pages and programming languages, enabling dynamic, interactive content. Browsers parse HTML into the DOM tree, allowing scripts to modify structure, style, and content in real-time.
innerHTML gets or sets HTML markup including tags, parsing and rendering HTML elements. It returns everything including hidden elements. innerText gets or sets only visible text content, excluding HTML tags and respecting CSS styling like display:none. innerHTML is faster but poses XSS risks with untrusted content. innerText is safer for displaying user input. textContent is similar to innerText but includes hidden text and is more performant.
Browser APIs2
The window object represents the browser window/tab, serving as the global object containing all JavaScript global variables, functions, and browser APIs like location, history, and setTimeout. The document object is a property of window, representing the loaded HTML document (DOM tree). Window controls browser features; document controls page content. window.document accesses the DOM; window controls the browser environment.
The BOM provides JavaScript access to browser functionality beyond the document, including window, navigator, location, history, and screen objects. It enables controlling browser windows, accessing browser information, manipulating URLs, managing browsing history, and detecting screen properties. Unlike the DOM which is standardized, BOM implementation varies between browsers. BOM is essential for browser-specific features like opening windows, redirecting pages, and accessing browser capabilities.
Web Storage1
Cookies are small data pieces (4KB) sent with HTTP requests, with expiration dates, accessible by both server and client. localStorage (5MB) persists data indefinitely across browser sessions and tabs, accessible only by client JavaScript. sessionStorage (5MB) stores data for the page session, cleared when tab closes, isolated per tab. Use cookies for server communication, localStorage for persistent client data, sessionStorage for temporary tab-specific data.
Iteration2
For objects: for...in loops over enumerable properties, Object.keys() returns key array, Object.values() returns value array, Object.entries() returns [key, value] pairs. For arrays: for loop with index, forEach() method, for...of loops over values, map() for transformations, entries() for index-value pairs. Choose based on needs: for...in for object properties, for...of for iterable values, forEach/map for array operations with callbacks.
for loops provide explicit control with break/continue, fastest performance, mutable operations, and imperative style. forEach iterates executing callbacks, no return value, cannot break, cleaner than for loops. map transforms arrays returning new array, functional style, chainable, ideal for transformations. Use for for complex control flow, forEach for side effects like logging, map for array transformations. map and forEach are more declarative and readable.
Scope1
Function scope means variables declared with var are accessible throughout the function regardless of block. Block scope means variables declared with let/const are only accessible within the closest {} block (if, for, while, etc.). Function scope can cause unexpected behavior with loops and conditionals. Block scope provides better encapsulation and prevents accidental variable access. Modern JavaScript prefers block-scoped let/const for clearer scoping and fewer bugs.
Programming Patterns1
Method chaining calls multiple methods sequentially on the same object by returning the object from each method, enabling fluent, readable code. Common with array methods: arr.filter().map().reduce(). Libraries like jQuery and Lodash extensively use chaining. Benefits include concise code, improved readability, and functional programming style. Implement by returning 'this' or the transformed object from each method. Chaining reduces intermediate variables and creates declarative pipelines.
Functional Programming2
A pure function always returns the same output for the same inputs and has no side effects (doesn't modify external state, make API calls, or change input parameters). Benefits include predictability, testability, cacheability through memoization, easier debugging, and safe parallelization. Pure functions are fundamental to functional programming. Example: (a, b) => a + b is pure; functions modifying globals or mutating arguments are impure.
Function composition combines multiple functions where one function's output becomes another's input, creating complex operations from simple functions. Compose functions right-to-left: compose(f, g, h)(x) equals f(g(h(x))). Pipe is left-to-right. Benefits include code reusability, modularity, easier testing, and declarative style. Implement using reduce or manually chaining functions. Composition is fundamental to functional programming, enabling building complex behaviors from small, focused functions.
Programming Concepts2
Side effects are operations that interact with or modify state outside their local scope: mutating variables, modifying objects/arrays, making API calls, DOM manipulation, console.log, file operations, or accessing Date/Math.random(). Side effects make code harder to test, reason about, and debug. Pure functions avoid side effects. Managing side effects involves isolating them, using immutable data, and clearly separating pure and impure code for maintainability.
Immutability means data cannot be changed after creation; instead, create modified copies. Primitive values are inherently immutable. For objects/arrays, use techniques like spread operator, Object.assign(), array methods returning new arrays (map, filter), or libraries like Immer. Benefits include predictable state changes, easier debugging, simpler change detection, safe concurrent operations, and enabling time-travel debugging. Immutability is central to functional programming and state management.
DOM Events2
mouseenter fires once when pointer enters the element, doesn't bubble, and ignores children. mouseover fires when entering element or its children, bubbles up the DOM, potentially firing multiple times. mouseenter paired with mouseleave; mouseover with mouseout. Use mouseenter for simple hover effects without child interference; mouseover when you need event bubbling or child element awareness. mouseenter provides simpler, more predictable hover behavior.
event.preventDefault() cancels the browser's default action for the event (form submission, link navigation, context menu) but allows event propagation. event.stopPropagation() prevents event from bubbling up or capturing down the DOM tree but doesn't prevent default actions. Use preventDefault() to override default behaviors while maintaining event flow; use stopPropagation() to limit event handling scope. They're independent and can be used together if needed.
Type System2
Type coercion is JavaScript's automatic or explicit conversion of values from one type to another. Implicit coercion happens with operators: '5' + 3 = '53', '5' - 3 = 2. Explicit coercion uses Number(), String(), Boolean(). Coercion rules are complex: truthy/falsy values, string concatenation vs arithmetic, equality comparisons. Understanding coercion prevents bugs and explains unexpected behavior. Use strict equality (===) to avoid implicit coercion issues.
Falsy values evaluate to false in boolean contexts: false, 0, -0, 0n, '', null, undefined, NaN. All other values are truthy, including '0', 'false', empty objects {}, empty arrays [], and functions. Understanding truthy/falsy is crucial for conditionals, logical operators, and default values. Use Boolean() or double negation (!!) to explicitly convert to boolean. Common pattern: value || defaultValue uses truthy check for defaults.
ES6+ Features2
Optional chaining (?.) safely accesses nested object properties without errors if intermediate values are null/undefined, returning undefined instead. Syntax: obj?.prop, obj?.[expr], func?.(). Short-circuits evaluation at first null/undefined. Prevents 'Cannot read property of undefined' errors. Useful for API responses, configuration objects, optional callbacks. Cleaner than repetitive existence checks. Modern JavaScript feature eliminating verbose null-checking code and improving readability for property access.
Nullish coalescing operator (??) returns right operand when left is null or undefined, otherwise returns left operand. Unlike OR (||) which checks truthiness, ?? specifically checks null/undefined. Example: 0 ?? 10 returns 0; 0 || 10 returns 10. Useful for default values when 0, '', or false are valid values. Prevents bugs from falsy value coercion. Modern feature for precise null/undefined handling distinct from general falsy checks.
Meta-programming2
Proxy creates a wrapper around objects allowing interception and customization of fundamental operations like property access, assignment, enumeration, function invocation. Define traps (handlers) for get, set, has, deleteProperty, apply, construct, etc. Use cases include validation, logging, data binding, default values, property access control, and creating reactive systems. Proxies enable meta-programming, implementing custom behavior transparently, and creating virtual objects with computed properties.
Reflect is a built-in object providing methods for interceptable JavaScript operations, companion to Proxy handlers. Methods mirror Proxy traps: Reflect.get(), Reflect.set(), Reflect.has(), etc. Benefits include consistent function API instead of operators, better return values (boolean success), proper error handling, and cleaner proxy trap implementations. Reflect makes meta-programming operations explicit and programmatic, improving code that manipulates objects dynamically.