Skip to main content

JavaScript Interview Questions (Fundamentals)

A comprehensive guide to JavaScript interview questions, organized by difficulty level. This collection covers fundamental concepts to advanced topics commonly asked in web development interviews.

Easy Level Questions

Answer: JavaScript is a high-level programming language widely used to create interactive effects within web browsers. A high-level programming language means it’s human-understandable and abstracted from machine code.Common Uses:
  • Web application development (front-end)
  • Server-side development (back-end with Node.js)
  • Mobile application development
  • Interactive web page features
Answer: Template literals are a feature that combines the dollar sign $, curly brackets {}, and backticks ` to make string interpolation easier and more readable.Without template literals:
let a = 10;
let b = 20;
console.log("The sum of " + a + " and " + b + " is " + (a + b));
With template literals:
let a = 10;
let b = 20;
console.log(`The sum of ${a} and ${b} is ${a + b}`);
Purpose:
  • Embed variables and expressions directly into strings
  • Make code more readable and flexible
  • Avoid complex string concatenation
Answer: Hoisting is a JavaScript mechanism where variable and function declarations are moved (or “hoisted”) to the top of their scope before code execution.Types of Hoisting:
  1. Function Hoisting:
greet(); // Works! Outputs: "Hello"

function greet() {
    console.log("Hello");
}
  1. Variable Hoisting:
console.log(x); // undefined
var x = 10;

console.log(y); // ReferenceError
let y = 20;

console.log(z); // ReferenceError
const z = 30;
Key Points:
  • var declarations are hoisted and initialized with undefined
  • let and const are hoisted but not initialized (temporal dead zone)
  • Function declarations are fully hoisted
Answer:
Featurevarletconst
ScopeFunction scopeBlock scopeBlock scope
ReassignmentYesYesNo
Re-declarationYesNoNo
HoistingHoisted with undefinedHoisted but not initializedHoisted but not initialized
Example:
if (true) {
    var a = 10;
    let b = 20;
    const c = 30;
}

console.log(a); // 10 (accessible - function scoped)
console.log(b); // ReferenceError (block scoped)
console.log(c); // ReferenceError (block scoped)
Best Practice:
  1. Avoid var due to its global accessibility and potential bugs
  2. Use const by default to prevent accidental reassignment
  3. Use let only when you need to reassign values
Answer:Primitive Data Types (Immutable):
  1. Number: Integer and floating-point numbers, including special values (Infinity, -Infinity, NaN)
let age = 25;
let pi = 3.14;
let infinity = Infinity;
  1. String: Sequence of characters
let name = 'John';
let message = "Hello";
let template = `Welcome`;
  1. Boolean: Logical entity with two values
let isActive = true;
let isCompleted = false;
  1. Undefined: Default value for declared but unassigned variables
let x;
console.log(x); // undefined
  1. Null: Intentional absence of value
let data = null;
  1. Symbol: Unique and immutable identifier
let sym1 = Symbol('id');
let sym2 = Symbol('id');
console.log(sym1 === sym2); // false
  1. BigInt: Very large integers beyond Number limit
let bigNumber = 1234567890123456789012345678901234567890n;
Non-Primitive Data Types (Mutable):
  1. Object: Collection of key-value pairs
let person = { name: 'John', age: 30 };
  1. Array: Ordered collection of values (zero-indexed)
let numbers = [1, 2, 3, 4, 5];
  1. Function: Reusable block of code
function greet() {
    console.log('Hello');
}
Answer: An array is an ordered collection of values or elements. Elements can be of any data type but typically belong to the same type.Creating Arrays:
// Array literal syntax
let fruits = ['apple', 'banana', 'orange'];

// Array constructor
let numbers = new Array(1, 2, 3, 4, 5);
Accessing Elements:
// Direct access using index
console.log(fruits[0]); // 'apple'
console.log(fruits[2]); // 'orange'

// Using for loop
for (let i = 0; i < fruits.length; i++) {
    console.log(fruits[i]);
}

// Using forEach
fruits.forEach(fruit => {
    console.log(fruit);
});
Answer:
OperatorNameType CoercionExample
==Equality operatorYes'5' == 5 → true
===Strict equality operatorNo'5' === 5 → false
Examples:
// Double equals (==) - performs type coercion
console.log('5' == 5);     // true (string converted to number)
console.log(true == 1);    // true
console.log(null == undefined); // true

// Triple equals (===) - no type coercion
console.log('5' === 5);    // false (different types)
console.log(true === 1);   // false
console.log(null === undefined); // false
Best Practice: Always use === (strict equality) to avoid unexpected type coercion bugs.
Answer: isNaN() is a special JavaScript function that checks if a given value is “Not a Number” (NaN). It returns true if the value cannot be coerced into a number.Examples:
// Returns true (cannot be converted to number)
console.log(isNaN('hello'));      // true
console.log(isNaN(undefined));    // true
console.log(isNaN(NaN));          // true

// Returns false (can be converted to number)
console.log(isNaN('123'));        // false (string converted to 123)
console.log(isNaN(123));          // false
console.log(isNaN(true));         // false (converted to 1)

// Interesting case
console.log(isNaN(null));         // false (null is treated as 0)
console.log(typeof NaN);          // 'number' (NaN is a number type!)
Answer:
Featureundefinednull
MeaningDefault absence of valueIntentional absence of value
AssignmentAutomatically assignedManually assigned
Typeundefinedobject (historical bug)
Use caseVariable declared but not initializedExplicitly setting “no value”
Examples:
// undefined - declared but not assigned
let a;
console.log(a);           // undefined
console.log(typeof a);    // 'undefined'

// null - intentionally empty
let b = null;
console.log(b);           // null
console.log(typeof b);    // 'object' (known JavaScript quirk)

// Comparison
console.log(null == undefined);  // true (loose equality)
console.log(null === undefined); // false (strict equality)
Answer: The typeof operator determines the data type of a given value or variable and returns a string indicating the type.Examples:
console.log(typeof 42);              // 'number'
console.log(typeof 'hello');         // 'string'
console.log(typeof true);            // 'boolean'
console.log(typeof undefined);       // 'undefined'
console.log(typeof null);            // 'object' (JavaScript quirk)
console.log(typeof Symbol('id'));    // 'symbol'
console.log(typeof 123n);            // 'bigint'
console.log(typeof {});              // 'object'
console.log(typeof []);              // 'object' (arrays are objects)
console.log(typeof function(){});    // 'function'
Use Cases:
  • Input validation
  • Conditional logic based on data types
  • Debugging and type checking

Medium Level Questions

Answer: The map() method creates a new array by applying a specified function to each element of an existing array. It doesn’t modify the original array.Example:
const numbers = [1, 2, 3, 4, 5];

// Traditional approach
const doubled = [];
for (let i = 0; i < numbers.length; i++) {
    doubled.push(numbers[i] * 2);
}

// Using map (cleaner)
const doubledNumbers = numbers.map(num => num * 2);
console.log(doubledNumbers); // [2, 4, 6, 8, 10]
console.log(numbers);        // [1, 2, 3, 4, 5] (unchanged)
Benefits:
  • Concise one-line transformations
  • Doesn’t mutate the original array
  • Functional programming approach
  • More readable than loops
Answer: Event bubbling and capturing are two phases of event propagation in the DOM.Event Capturing (Top to Bottom): Events are handled from the outermost element to the innermost element.
  • Like information flowing from CEO → Manager → Employee
Event Bubbling (Bottom to Top): Events are handled from the innermost element to the outermost element.
  • Like concerns flowing from Employee → Manager → CEO
Example:
// HTML structure
<div id="outer">
    <div id="mid">
        <div id="inner">Click me</div>
    </div>
</div>

// Event Bubbling (default)
document.getElementById('outer').addEventListener('click', () => {
    console.log('Outer div clicked');
});
document.getElementById('mid').addEventListener('click', () => {
    console.log('Mid div clicked');
});
document.getElementById('inner').addEventListener('click', () => {
    console.log('Inner div clicked');
});

// Click on inner div outputs:
// Inner div clicked
// Mid div clicked
// Outer div clicked

// Event Capturing (set third parameter to true)
document.getElementById('outer').addEventListener('click', () => {
    console.log('Outer div clicked');
}, true);

// Click on inner div outputs:
// Outer div clicked
// Mid div clicked
// Inner div clicked
Answer: A higher-order function is a function that either:
  1. Takes another function as an argument, OR
  2. Returns a function as a result
Examples:
// map() is a higher-order function
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(function(num) {
    return num * 2;
});

// Function that accepts a function as argument
function operate(a, b, operation) {
    return operation(a, b);
}

const add = (x, y) => x + y;
const multiply = (x, y) => x * y;

console.log(operate(5, 3, add));      // 8
console.log(operate(5, 3, multiply)); // 15

// Function that returns a function
function createMultiplier(multiplier) {
    return function(number) {
        return number * multiplier;
    };
}

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

console.log(double(5));  // 10
console.log(triple(5));  // 15
Common Higher-Order Functions:
  • map(), filter(), reduce()
  • forEach(), find(), some(), every()
  • setTimeout(), setInterval()
Answer: An IIFE is a JavaScript function that is defined and executed immediately after its creation, without needing to be called explicitly.Regular Function:
function greet() {
    console.log("Hello World");
}
greet(); // Must be called manually
IIFE:
(function() {
    console.log("Hello World");
})();
// Executes automatically, outputs: "Hello World"
Benefits:
  • No name needed (anonymous function)
  • Executes immediately without calling
  • Creates private scope (data encapsulation)
  • Avoids polluting global namespace
Use Cases:
// Private variables
(function() {
    let privateVar = "secret";
    console.log(privateVar); // Accessible here
})();
// console.log(privateVar); // Error: not accessible outside

// Module pattern
const counter = (function() {
    let count = 0;
    return {
        increment: () => ++count,
        getCount: () => count
    };
})();

console.log(counter.increment()); // 1
console.log(counter.getCount());  // 1
Answer: A closure is a function that remembers the environment (variables) in which it was created, even after the outer function has finished executing.Example:
function outerFunction() {
    let outerVariable = "I'm from outer function";
    
    function innerFunction() {
        console.log(outerVariable); // Accesses outer variable
    }
    
    return innerFunction;
}

const closureFunction = outerFunction();
closureFunction(); // "I'm from outer function"
Practical Use Case - Private Variables:
function createCounter() {
    let count = 0; // Private variable
    
    return {
        increment: function() {
            count++;
            return count;
        },
        decrement: function() {
            count--;
            return count;
        },
        getCount: function() {
            return `Current count: ${count}`;
        }
    };
}

const myCounter = createCounter();
console.log(myCounter.increment());  // 1
console.log(myCounter.increment());  // 2
console.log(myCounter.decrement());  // 1
console.log(myCounter.getCount());   // "Current count: 1"
// console.log(count); // Error: count is not accessible
Why Closures? JavaScript doesn’t have a built-in way to declare private variables in objects. Closures provide this functionality by maintaining access to variables from their outer scope.
Answer: Both are Web APIs that schedule code execution after specific time delays.setTimeout() - Execute once after delay:
setTimeout(() => {
    console.log("Hello after 2 seconds");
}, 2000); // 2000 milliseconds = 2 seconds

// With clearTimeout
const timerId = setTimeout(() => {
    console.log("This won't run");
}, 2000);

clearTimeout(timerId); // Cancels the timeout
setInterval() - Execute repeatedly at intervals:
const intervalId = setInterval(() => {
    console.log("Hello every 2 seconds");
}, 2000);

// Stop after 4 seconds using setTimeout
setTimeout(() => {
    clearInterval(intervalId);
    console.log("Interval stopped");
}, 4000);
Practical Example - Clock:
const clockInterval = setInterval(() => {
    const now = new Date();
    console.log(now.toLocaleTimeString());
}, 1000); // Update every second

// Stop clock after 10 seconds
setTimeout(() => {
    clearInterval(clockInterval);
}, 10000);
Use Cases:
  • setTimeout(): Notifications, delayed actions, debouncing
  • setInterval(): Clocks, real-time updates, polling APIs
Answer: Promises handle asynchronous tasks by providing a structured approach for managing success and failure states, avoiding callback hell.Promise States:
  1. Pending: Initial state, neither fulfilled nor rejected
  2. Fulfilled: Operation completed successfully
  3. Rejected: Operation failed
Example:
const data = { name: 'John', age: 30 };

function fetchData() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            // Simulate API call
            if (data) {
                resolve(data); // Success
            } else {
                reject('Data not found'); // Failure
            }
        }, 2000);
    });
}

// Using the promise
fetchData()
    .then(data => {
        console.log('Data fetched:', data);
    })
    .catch(error => {
        console.error('Error:', error);
    });
Chaining Promises:
fetchData()
    .then(data => {
        console.log(data);
        return data.name;
    })
    .then(name => {
        console.log('Name:', name);
    })
    .catch(error => {
        console.error('Error:', error);
    });
Benefits:
  • Better error handling than callbacks
  • Cleaner code structure
  • Avoids callback hell
  • Chainable operations
Answer: async/await provides a cleaner syntax for working with Promises, making asynchronous code look and behave more like synchronous code.Key Points:
  • async function always returns a Promise
  • await pauses execution until Promise resolves
  • Must use await inside async function
  • Code continues other tasks while waiting (non-blocking)
Example:
async function fetchData() {
    try {
        // Wait for API response
        const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
        
        // Wait for JSON parsing
        const data = await response.json();
        
        console.log('Data fetched:', data);
        return data;
    } catch (error) {
        console.error('Error:', error);
    }
}

fetchData();
Comparison - Promise vs Async/Await:
// Using Promises
fetch('https://api.example.com/data')
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => console.error(error));

// Using Async/Await (cleaner)
async function getData() {
    try {
        const response = await fetch('https://api.example.com/data');
        const data = await response.json();
        console.log(data);
    } catch (error) {
        console.error(error);
    }
}
Real-World Analogy: Like downloading an app from Play Store - it downloads in the background while you can use other apps. Once downloaded, you get a notification.
Answer: All three methods allow you to set the this context for a function, but they differ in how they handle arguments and execution.call() - Executes immediately, arguments passed individually:
function cook(ingredient1, ingredient2) {
    console.log(`${this.name} is having a meal with ${ingredient1} and ${ingredient2}`);
}

const person = { name: 'Adam' };

cook.call(person, 'rice', 'beans');
// Output: "Adam is having a meal with rice and beans"
apply() - Executes immediately, arguments passed as array:
cook.apply(person, ['rice', 'beans']);
// Output: "Adam is having a meal with rice and beans"
bind() - Returns new function, executes later:
const cookForAdam = cook.bind(person, 'rice', 'beans');
// Function created but not executed yet

cookForAdam(); // Now it executes
// Output: "Adam is having a meal with rice and beans"
Comparison Table:
MethodExecutionArgumentsReturns
call()ImmediateIndividualResult
apply()ImmediateArrayResult
bind()LaterIndividual/ArrayNew function
Restaurant Analogy:
  • call: Waiter takes orders one by one, tells chef immediately
  • apply: Waiter writes all orders in a list, tells chef immediately
  • bind: Waiter takes orders, tells chef to cook when ready
Why Use These? To explicitly set what this refers to, avoiding confusion in complex code.
Answer: Event delegation is a technique that uses a single event listener on a parent element to manage events for multiple child elements, utilizing event bubbling.Without Event Delegation:
// Inefficient - multiple listeners
const items = document.querySelectorAll('li');
items.forEach(item => {
    item.addEventListener('click', function() {
        console.log('Clicked:', this.textContent);
    });
});
With Event Delegation:
// Efficient - single listener on parent
const itemList = document.getElementById('item-list');
itemList.addEventListener('click', function(e) {
    if (e.target.tagName === 'LI') {
        console.log('Clicked:', e.target.textContent);
    }
});
Benefits:
  1. Better Performance: One listener instead of many
  2. Handles Dynamic Elements: Works for elements added later
  3. Less Memory Usage: Fewer event listeners
  4. Cleaner Code: Centralized event handling
Example with Dynamic Elements:
<ul id="item-list">
    <li>Item 1</li>
    <li>Item 2</li>
</ul>

<script>
document.getElementById('item-list').addEventListener('click', (e) => {
    if (e.target.tagName === 'LI') {
        console.log('Clicked:', e.target.textContent);
    }
});

// Add new item dynamically
const newItem = document.createElement('li');
newItem.textContent = 'Item 3';
document.getElementById('item-list').appendChild(newItem);
// Click still works on new item!
</script>

Hard Level Questions

Answer: The event loop is the mechanism that allows JavaScript to perform non-blocking operations despite being single-threaded. It manages the execution of synchronous and asynchronous code.How It Works:
  1. Call Stack: Executes synchronous code
  2. Web APIs: Handle asynchronous operations (setTimeout, fetch, etc.)
  3. Task Queue: Stores completed async tasks
  4. Event Loop: Checks if call stack is empty, then moves tasks from queue to stack
Restaurant Analogy:
  • Chef = Main thread (call stack)
  • Boiling water = Async operation
  • Assistant = Event loop (checks if tasks are complete)
The chef doesn’t wait for water to boil; she chops vegetables. The assistant monitors the water and notifies the chef when it’s ready.Example:
console.log('1: Start');

setTimeout(() => {
    console.log('2: Timeout');
}, 0);

console.log('3: End');

// Output:
// 1: Start
// 3: End
// 2: Timeout (even with 0ms delay!)
Why this order?
  1. “Start” executes (synchronous)
  2. setTimeout sends callback to Web API
  3. “End” executes (synchronous)
  4. Call stack empty → event loop moves timeout callback to stack
  5. “Timeout” executes
Process Flow:
Call Stack → Web APIs → Task Queue → Event Loop → Call Stack
Answer: async/await is syntactic sugar built on top of Promises, providing cleaner syntax while Promises are the underlying mechanism.Key Differences:
FeaturePromiseAsync/Await
Syntax.then() chainsTry/catch blocks
ReadabilityCan get messyCleaner, synchronous-like
Error Handling.catch()try/catch
RelationshipFoundationBuilt on Promises
Promise Chaining:
fetchData1()
    .then(result1 => {
        return fetchData2(result1);
    })
    .then(result2 => {
        console.log(result2);
    })
    .catch(error => {
        console.error(error);
    });
Async/Await (Cleaner):
async function fetchData() {
    try {
        const result1 = await fetchData1();
        const result2 = await fetchData2(result1);
        console.log(result2);
    } catch (error) {
        console.error(error);
    }
}

fetchData();
When to Use:
  • Promises: Simple async operations, library compatibility
  • Async/Await: Complex async flows, better readability needed
Important: Async/await doesn’t replace Promises; it works with them!
Answer: The reduce() method reduces an array to a single value by applying a function to each element, accumulating the result.Syntax:
array.reduce((accumulator, currentValue) => {
    // return updated accumulator
}, initialValue);
How It Works:
const numbers = [1, 2, 3, 4, 5];

const sum = numbers.reduce((accumulator, currentValue) => {
    return accumulator + currentValue;
}, 0);

console.log(sum); // 15
Step-by-Step Execution:
StepAccumulatorCurrent ValueResult
10 (initial)11
2123
3336
46410
510515
Common Use Cases:
// Sum of numbers
const total = [1, 2, 3, 4].reduce((acc, num) => acc + num, 0); // 10

// Find maximum
const max = [1, 5, 3, 9, 2].reduce((acc, num) => Math.max(acc, num)); // 9

// Count occurrences
const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];
const count = fruits.reduce((acc, fruit) => {
    acc[fruit] = (acc[fruit] || 0) + 1;
    return acc;
}, {});
// { apple: 3, banana: 2, orange: 1 }

// Flatten array
const nested = [[1, 2], [3, 4], [5, 6]];
const flat = nested.reduce((acc, arr) => acc.concat(arr), []);
// [1, 2, 3, 4, 5, 6]
Benefits:
  • Concise (one line instead of loops)
  • Functional programming approach
  • Versatile (sum, average, flatten, group, etc.)
  • Doesn’t mutate original array
Answer: Currying is a functional programming technique where a function with multiple arguments is transformed into a sequence of functions, each taking a single argument.Regular Function:
function add(a, b) {
    return a + b;
}
console.log(add(2, 3)); // 5
Curried Function:
function multiply(a) {
    return function(b) {
        return a * b;
    };
}

// Usage
const double = multiply(2); // a is fixed to 2
console.log(double(3));      // 6
console.log(double(5));      // 10

const triple = multiply(3);  // a is fixed to 3
console.log(triple(4));      // 12
PowerPoint Analogy: Creating presentations for 5 animals:
  • Template = Fixed parameter (constant)
  • Animal content = Variable parameter (dynamic)
Instead of creating 50 slides (10 slides × 5 animals), create one 10-slide template and reuse it for each animal.Practical Example:
// Curried logging function
function log(level) {
    return function(message) {
        console.log(`[${level}] ${message}`);
    };
}

const error = log('ERROR');
const info = log('INFO');

error('Database connection failed'); // [ERROR] Database connection failed
info('Server started');              // [INFO] Server started

// Curried discount calculator
function discount(percentage) {
    return function(price) {
        return price - (price * percentage / 100);
    };
}

const tenPercent = discount(10);
const twentyPercent = discount(20);

console.log(tenPercent(100));   // 90
console.log(twentyPercent(100)); // 80
Benefits:
  • Reusability: Fix common parameters, reuse function
  • Flexibility: Create specialized functions from general ones
  • Performance: Reduce redundant parameter passing
  • Code Organization: Cleaner, more modular code
Answer: A generator function is a special function that can pause its execution and resume later, returning multiple values over time using the yield keyword.Syntax:
function* generatorName() {
    yield value1;
    yield value2;
    // ...
}
Basic Example:
function* infiniteSequence() {
    let num = 1;
    while (true) {
        yield num;
        num++;
    }
}

const sequence = infiniteSequence();

console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 2
console.log(sequence.next().value); // 3
Key Difference from Regular Functions:
  • Regular: Runs completely, then returns
  • Generator: Pauses at each yield, resumes on next()
Preventing Infinite Loops:
function* controlledSequence() {
    let num = 1;
    while (true) {
        yield num++;
    }
}

const gen = controlledSequence();

// Controlled execution
for (let i = 0; i < 5; i++) {
    console.log(gen.next().value);
}
// Output: 1, 2, 3, 4, 5 (stops after 5)
Practical Use Cases:
// Pagination
function* paginate(items, pageSize) {
    for (let i = 0; i < items.length; i += pageSize) {
        yield items.slice(i, i + pageSize);
    }
}

const data = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const pages = paginate(data, 3);

console.log(pages.next().value); // [1, 2, 3]
console.log(pages.next().value); // [4, 5, 6]
console.log(pages.next().value); // [7, 8, 9]

// ID Generator
function* idGenerator() {
    let id = 1;
    while (true) {
        yield `ID-${id++}`;
    }
}

const ids = idGenerator();
console.log(ids.next().value); // ID-1
console.log(ids.next().value); // ID-2
Benefits:
  • Lazy evaluation (compute only when needed)
  • Memory efficient
  • Prevents infinite loop crashes
  • Useful for iterating large datasets
Answer: WeakMap and WeakSet are special collections for storing temporary object references that don’t prevent garbage collection.WeakMap:
  • Stores key-value pairs where keys must be objects
  • Keys are weakly referenced (can be garbage collected)
  • Not iterable, no size property
WeakSet:
  • Stores unique object references
  • Only accepts objects, not primitives
  • Not iterable, no size property
Example - WeakMap:
const weakMap = new WeakMap();
let obj = { name: 'Adam' };

// Add key-value pair
weakMap.set(obj, 'employee');

console.log(weakMap.get(obj)); // 'employee'

// Remove reference - object becomes garbage collected
obj = null;
console.log(weakMap.get(obj)); // undefined
Example - WeakSet:
const weakSet = new WeakSet();
let obj = { id: 1 };

weakSet.add(obj);
console.log(weakSet.has(obj)); // true

obj = null;
console.log(weakSet.has(obj)); // false
Key Differences:
FeatureMap/SetWeakMap/WeakSet
Keys/ValuesAny typeObjects only
IterationIterableNot iterable
Size propertyYesNo
Garbage CollectionPrevents GCAllows GC
Use casePermanent storageTemporary/cache
Use Cases:
  • Cache management: Store temporary data that can be cleaned up
  • Private data: Associate metadata with objects
  • Memory leaks prevention: Automatic cleanup when objects are no longer needed
Why Use Them? Regular Maps/Sets hold strong references, preventing garbage collection. WeakMap/WeakSet allow automatic cleanup, improving memory efficiency.
Answer: JavaScript manages memory automatically through two main processes: memory allocation and garbage collection.1. Memory Allocation:Stack Memory (Temporary):
  • Stores primitive data types
  • Fixed size, fast access
  • Automatically cleaned when out of scope
let x = 10;           // Stored in stack
let name = 'John';    // Stored in stack
let flag = true;      // Stored in stack
Heap Memory (Permanent):
  • Stores non-primitive data types (objects, arrays, functions)
  • Dynamic size, slower access
  • Requires garbage collection
let person = { name: 'John' };  // Object in heap
let numbers = [1, 2, 3];        // Array in heap
2. Garbage Collection (Mark and Sweep):JavaScript uses the “Mark-and-Sweep” algorithm:Step 1 - Mark:
let x = 10;              // References 10
let name = 'Sara';       // References 'Sara'
let flag = true;         // References true
let tableJoker = null;   // References nothing (marked)
let bookData = { title: 'Book' }; // References object
Step 2 - Sweep:
  • Variables referencing null or out of scope are garbage collected
  • Memory is freed for new allocations
Visual Example:
Variables → References → Objects
---------------------------------
x          → 10
name       → 'Sara'
flag       → true
tableJoker → null        [MARKED FOR DELETION]
bookData   → { object }
Memory Leaks to Avoid:
// Bad: Global variables never garbage collected
var globalData = new Array(1000000);

// Good: Use local scope
function processData() {
    let localData = new Array(1000000);
    // ... use data
    // Automatically freed when function ends
}

// Bad: Forgotten event listeners
element.addEventListener('click', handler);
// If element removed but listener not removed = memory leak

// Good: Remove listeners
element.removeEventListener('click', handler);
Automatic Process: JavaScript handles this automatically, but understanding it helps write memory-efficient code.
Answer: Shallow and deep copies differ in how they handle nested objects and references.Shallow Copy:
  • Copies main properties
  • Nested objects/arrays remain referenced (not copied)
  • Changes to nested items affect original
Deep Copy:
  • Copies everything, including nested objects
  • Creates completely independent clone
  • Changes don’t affect original
Analogy:
  • Shallow Copy: Photo of a tree (change photo, tree changes - cursed photo!)
  • Deep Copy: Clone of a tree (change clone, original unaffected)
Shallow Copy Examples:
const original = {
    name: 'John',
    address: {
        city: 'Delhi'
    }
};

// Method 1: Object.assign()
const shallowCopy1 = Object.assign({}, original);

// Method 2: Spread operator
const shallowCopy2 = { ...original };

// Problem: Modifying nested object affects original
shallowCopy2.address.city = 'Mumbai';
console.log(original.address.city); // 'Mumbai' (original changed!)
Deep Copy Examples:
const original = {
    name: 'John',
    address: {
        city: 'Delhi'
    }
};

// Method 1: JSON parse/stringify (most common)
const deepCopy = JSON.parse(JSON.stringify(original));

// Safe: Modifying nested object doesn't affect original
deepCopy.address.city = 'Mumbai';
console.log(original.address.city); // 'Delhi' (original unchanged!)
console.log(deepCopy.address.city); // 'Mumbai'

// Method 2: structuredClone (modern browsers)
const deepCopy2 = structuredClone(original);
Comparison:
OperationShallow CopyDeep Copy
Top-level propertiesCopiedCopied
Nested objectsReferencedCopied
IndependencePartialComplete
PerformanceFastSlower
Methods{...obj}, Object.assign()JSON.parse(JSON.stringify())
When to Use:
  • Shallow: Simple objects, performance critical
  • Deep: Complex nested structures, complete independence needed
Answer: Strict mode is a way to opt into a restricted variant of JavaScript that catches common coding errors and prevents unsafe actions.Enable Strict Mode:
'use strict';
Benefits of Strict Mode:1. Prevents Undeclared Variables:
'use strict';
x = 10; // Error: x is not defined

// Without strict mode
x = 10; // Works, creates global variable (bad!)
2. Prevents Assignment to Read-Only Properties:
'use strict';
const obj = {};
Object.defineProperty(obj, 'prop', {
    value: 40,
    writable: false
});

obj.prop = 50; // Error: Cannot assign to read-only property

// Without strict mode: silently fails (dangerous!)
3. Prevents this as Global Object:
'use strict';
function show() {
    console.log(this);
}
show(); // undefined

// Without strict mode
show(); // Window object (data leak risk!)
4. Disallows with Statement:
'use strict';
with (Math) {
    console.log(sqrt(4)); // Error: with not allowed
}

// Without strict mode: works but unpredictable
5. Secures eval():
'use strict';
eval('var x = 10');
console.log(x); // Error: x is not defined (contained in eval)

// Without strict mode: x leaks to outer scope
6. Prevents Duplicate Parameters:
'use strict';
function sum(a, a, c) { // Error: Duplicate parameter name
    return a + a + c;
}

// Without strict mode: silently uses last 'a'
How to Enable:
// Global scope (entire file)
'use strict';
function myFunction() {
    // strict mode
}

// Function scope (specific function)
function myFunction() {
    'use strict';
    // strict mode only in this function
}
When to Use:
  • Modern JavaScript development (always recommended)
  • Prevents common mistakes
  • Makes debugging easier
  • Required for ES6 modules (automatic)
Answer: The Observer pattern is a design pattern where an object (subject) maintains a list of dependents (observers) and notifies them automatically of state changes.Components:
  1. Subject: Object being observed (e.g., YouTube channel)
  2. Observers: Objects watching the subject (e.g., subscribers)
  3. Notification: Mechanism to alert observers (e.g., notifications)
Real-World Analogy:
  • Influencer (Subject) posts new content
  • Subscribers (Observers) get notified
  • YouTube (Platform) handles notification system
Implementation Example:
// Subject (Observable)
class YouTubeChannel {
    constructor() {
        this.subscribers = [];
    }
    
    subscribe(observer) {
        this.subscribers.push(observer);
    }
    
    unsubscribe(observer) {
        this.subscribers = this.subscribers.filter(sub => sub !== observer);
    }
    
    notify(video) {
        this.subscribers.forEach(subscriber => {
            subscriber.update(video);
        });
    }
    
    uploadVideo(video) {
        console.log(`Uploading: ${video}`);
        this.notify(video);
    }
}

// Observers
class Subscriber {
    constructor(name) {
        this.name = name;
    }
    
    update(video) {
        console.log(`${this.name} notified: New video "${video}"`);
    }
}

// Usage
const channel = new YouTubeChannel();

const alice = new Subscriber('Alice');
const bob = new Subscriber('Bob');

channel.subscribe(alice);
channel.subscribe(bob);

channel.uploadVideo('JavaScript Tutorial');
// Output:
// Uploading: JavaScript Tutorial
// Alice notified: New video "JavaScript Tutorial"
// Bob notified: New video "JavaScript Tutorial"
JavaScript Event Listener (Observer Pattern):
const button = document.getElementById('myButton');

// Observer 1
button.addEventListener('click', () => {
    console.log('Observer 1 notified');
});

// Observer 2
button.addEventListener('click', () => {
    console.log('Observer 2 notified');
});

// Click button → both observers notified
Benefits:
  • Loose Coupling: Subject doesn’t need to know observer details
  • Dynamic Relationships: Add/remove observers at runtime
  • Broadcast Communication: One-to-many notifications
  • Scalability: Easy to add new observers
Use Cases in JavaScript:
  • Event listeners (DOM events)
  • State management (Redux, MobX)
  • Pub/Sub systems
  • Real-time updates (WebSocket notifications)