interview-questions

The Ultimate JavaScript & TypeScript Interview Guide

Welcome to the definitive guide for preparing for JavaScript and TypeScript technical interviews. This resource is designed for candidates of all levels, from junior to senior, and aims to provide a deep, practical, and comprehensive overview of the concepts you’ll need to master.

This guide is structured into three main parts:

  1. JavaScript Core Concepts: The fundamental building blocks of the language.
  2. TypeScript Concepts: Essential knowledge for modern, type-safe application development.
  3. Data Structures & Algorithms: Practical coding challenges to hone your problem-solving skills.

Each question is followed by a detailed explanation, one or more code solutions, and a complexity analysis where applicable.

Table of Contents


Part 1: JavaScript Core Concepts

1.1 Scope, Hoisting, and Variables

Q1: What’s the difference between var, let, and const?

Question: Explain the key differences in scoping, hoisting, and reassignment for var, let, and const.

Answer:

Code Example:

function scopeTest() {
  var a = 1;
  if (true) {
    var a = 2; // Re-declares and reassigns 'a'
    let b = 3;
    const c = 4;
    console.log(b, c); // 3, 4
  }
  // console.log(b); // ReferenceError: b is not defined
  console.log(a); // 2 (function-scoped var was overwritten)
}
scopeTest();

Q2: What is hoisting in JavaScript?

Question: Explain hoisting and provide code examples for variable and function hoisting.

Answer: Hoisting is JavaScript’s behavior of moving declarations to the top of their scope during compilation.

Code Example:

console.log(myVar); // undefined
var myVar = 5;

// console.log(myLet); // ReferenceError: Cannot access 'myLet' before initialization
let myLet = 10;

sayHello(); // "Hello!"
function sayHello() {
  console.log("Hello!");
}

// sayGoodbye(); // TypeError: sayGoodbye is not a function
var sayGoodbye = function() {
  console.log("Goodbye!");
};

1.2 Closures and Currying

Q3: What is a closure?

Question: Define what a closure is and provide a practical example.

Answer: A closure is the combination of a function and the lexical environment within which that function was declared. It gives an inner function access to an outer function’s scope, even after the outer function has returned. This is used for data privacy, stateful functions, and functional patterns.

Code Example (Data Privacy):

function createCounter() {
  let count = 0; // Private variable
  return {
    increment: () => count++,
    getValue: () => count,
  };
}

const counter = createCounter();
counter.increment();
console.log(counter.getValue()); // 1
// console.log(counter.count); // undefined

Q4: Explain the classic setTimeout loop problem.

Question: What is logged by the following code and how do you fix it?

for (var i = 1; i <= 5; i++) {
  setTimeout(() => console.log(i), 1000);
}

Answer: It logs 6 five times. The setTimeout callbacks run after the loop completes, by which time i (a single, function-scoped variable) is 6.

Solutions:

  1. Use let: let is block-scoped, so each iteration gets its own i.
    for (let i = 1; i <= 5; i++) {
      setTimeout(() => console.log(i), 1000); // Logs 1, 2, 3, 4, 5
    }
    
  2. Use a Closure (IIFE): Create a new scope for each iteration.
    for (var i = 1; i <= 5; i++) {
      (function(j) {
        setTimeout(() => console.log(j), 1000);
      })(i);
    }
    

Q5: What is currying? Implement a generic curry function.

Question: Explain currying and provide a generic implementation that can transform a function f(a, b, c) into curry(f)(a)(b)(c).

Answer: Currying transforms a function with multiple arguments into a sequence of nested functions, each taking a single argument.

Implementation:

function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } else {
      return function(...args2) {
        return curried.apply(this, [...args, ...args2]);
      }
    }
  };
}

const sum = (a, b, c) => a + b + c;
const curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3)); // 6
console.log(curriedSum(1, 2)(3)); // 6

1.3 Asynchronous JavaScript

Q6: Explain Promises and their three states.

Question: What is a Promise and what are its states?

Answer: A Promise is an object representing the eventual completion or failure of an asynchronous operation.

  1. pending: Initial state, neither fulfilled nor rejected.
  2. fulfilled: The operation completed successfully.
  3. rejected: The operation failed.

Once settled (fulfilled or rejected), a promise is immutable.


Q7: Promise.all vs race vs allSettled vs any?

Question: Describe the behavior of these four static Promise methods.

Answer:


Q8: What are async/await and how do they work?

Question: Explain async/await syntax and its relation to Promises.

Answer: async/await is syntactic sugar for Promises, making async code look synchronous.

Code Example:

const fetchData = () => new Promise(resolve => setTimeout(() => resolve("Data"), 1000));

async function getData() {
  try {
    console.log("Fetching...");
    const data = await fetchData(); // Pauses here
    console.log(data); // "Data"
  } catch (e) {
    console.error(e);
  }
}
getData();

Q9: Implement Promise.all from scratch.

Question: Write a function that mimics the behavior of Promise.all.

Answer:

function promiseAll(promises) {
  return new Promise((resolve, reject) => {
    const results = [];
    let completed = 0;
    if (promises.length === 0) {
      resolve(results);
      return;
    }
    promises.forEach((promise, index) => {
      Promise.resolve(promise) // Handle non-promise values
        .then(value => {
          results[index] = value;
          completed++;
          if (completed === promises.length) {
            resolve(results);
          }
        })
        .catch(reject); // Reject immediately on any error
    });
  });
}

// Example:
const p1 = Promise.resolve(3);
const p2 = 42;
const p3 = new Promise((resolve) => setTimeout(resolve, 100, 'foo'));
promiseAll([p1, p2, p3]).then(values => console.log(values)); // [3, 42, "foo"]

1.4 The this Keyword and Arrow Functions

Q10: Explain the rules for determining this.

Question: What are the main rules that determine the value of this?

Answer:

  1. Global Context: this is the global object (window/global) or undefined in strict mode.
  2. Function Call: this is the global object or undefined in strict mode.
  3. Method Call (obj.method()): this is the object (obj).
  4. call, apply, bind: this is explicitly set.
  5. Constructor Call (new Fn()): this is the new instance being created. Precedence: new > explicit > implicit > default.

Q11: How do arrow functions handle this?

Question: Explain the key difference in this binding between regular and arrow functions.

Answer: Arrow functions do not have their own this binding. They inherit this lexically from their surrounding scope. This avoids the need for that = this or .bind(this) in callbacks.

Code Example:

function Timer() {
  this.seconds = 0;
  setInterval(() => {
    this.seconds++; // `this` is the Timer instance
    console.log(this.seconds);
  }, 1000);
}
const timer = new Timer();

1.5 Prototypes and Inheritance

Q12: What is prototypal inheritance?

Question: Explain how JavaScript objects inherit from other objects.

Answer: Every object has a [[Prototype]] link to another object. When a property is accessed, the engine checks the object, then its prototype, then its prototype’s prototype, and so on, up the “prototype chain” until the property is found or the chain ends (null).

Code Example:

const animal = {
  speak() { console.log(`${this.name} makes a noise.`); }
};
const dog = Object.create(animal);
dog.name = "Rex";
dog.speak(); // "Rex makes a noise."

Q13: What’s the difference between __proto__, prototype, and Object.create()?

Question: Explain __proto__, prototype and how Object.create() relates.

Answer:


1.6 Event Handling and the Event Loop

Q14: Explain the JavaScript Event Loop.

Question: Describe the roles of the Call Stack, Callback Queue, and Microtask Queue.

Answer: The Event Loop allows single-threaded JS to be non-blocking.

  1. Call Stack: Tracks function execution.
  2. Web APIs: Handle async tasks (e.g., setTimeout).
  3. Microtask Queue: High-priority queue for Promise callbacks (.then, await).
  4. Callback Queue (Task Queue): Lower-priority queue for setTimeout, click events, etc.

Process: The loop runs tasks from the Call Stack. When the stack is empty, it runs all tasks from the Microtask Queue, then one task from the Callback Queue.


Q15: What is event delegation?

Question: Explain event delegation and its benefits.

Answer: Event delegation is attaching a single event listener to a parent element to manage events for all its children. It works because events “bubble up” the DOM tree. We use event.target to identify the child that triggered the event. Benefits: Better performance (fewer listeners) and automatic handling for dynamically added elements.

Code Example:

document.getElementById('parent-list').addEventListener('click', (event) => {
  if (event.target && event.target.matches('li.item')) {
    console.log('List item clicked:', event.target.textContent);
  }
});

1.7 Advanced Concepts and Techniques

Q16: What is memoization?

Question: Explain memoization and provide an example.

Answer: Memoization is an optimization technique that caches the results of expensive function calls and returns the cached result for the same inputs, avoiding repeated computation.

Code Example (Memoized Fibonacci):

function memoize(fn) {
  const cache = {};
  return function(...args) {
    const key = JSON.stringify(args);
    if (key in cache) {
      return cache[key];
    }
    const result = fn(...args);
    cache[key] = result;
    return result;
  };
}

const fib = (n) => {
  if (n <= 1) return n;
  return fib(n - 1) + fib(n - 2); // Note: For this to work with the generic memoizer, the recursive calls should be to the memoized version.
};

const memoizedFib = memoize(function fib(n) {
    if (n <= 1) return n;
    return memoizedFib(n - 1) + memoizedFib(n - 2);
});

console.log(memoizedFib(40)); // 102334155

Q17: Debounce vs. Throttle

Question: Explain the difference between debouncing and throttling.

Answer:


Q18: Implement a debounce function.

Question: Write a function that implements debouncing.

Answer:

function debounce(func, delay) {
  let timeoutId;

  return function(...args) {
    const context = this;
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => {
      func.apply(context, args);
    }, delay);
  };
}

// Usage:
const expensiveOperation = () => console.log('Performing expensive operation...');
const debouncedOp = debounce(expensiveOperation, 500);
// window.addEventListener('resize', debouncedOp);

Q19: Implement a throttle function.

Question: Write a function that implements throttling.

Answer:

function throttle(func, limit) {
  let inThrottle = false;
  return function(...args) {
    const context = this;
    if (!inThrottle) {
      func.apply(context, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}

// Usage:
const onScroll = () => console.log('Scrolled!');
const throttledScroll = throttle(onScroll, 1000);
// window.addEventListener('scroll', throttledScroll);

Part 2: TypeScript Concepts

2.1 Types, Interfaces, and Enums

Q20: interface vs type alias?

Question: Explain the differences between an interface and a type alias.

Answer:


Q21: What are Enums in TypeScript?

Question: Explain enums and give examples of numeric and string enums.

Answer: Enums define a set of named constants.


2.2 Generics

Q22: What are Generics and why are they useful?

Question: Explain generics in TypeScript and the problem they solve.

Answer: Generics create reusable, type-safe components that can work with a variety of types. They allow you to define a “type variable” instead of using a specific type or any, preserving type information.

Code Example:

function identity<T>(arg: T): T {
  return arg;
}
let output = identity<string>("myString"); // Type is string

Q23: Implement a generic Stack class.

Question: Create a Stack class that works with any data type.

Answer:

class Stack<T> {
  private storage: T[] = [];
  push(item: T): void { this.storage.push(item); }
  pop(): T | undefined { return this.storage.pop(); }
  peek(): T | undefined { return this.storage[this.storage.length - 1]; }
  size(): number { return this.storage.length; }
}

2.3 Advanced Types and Utility Types

Q24: Explain common Utility Types: Partial, Readonly, Pick, Omit.

Question: Demonstrate how to use four common utility types.

Answer: Utility types transform existing types.

interface User { id: number; name: string; email: string; }

// 1. Partial<T>: Makes all properties optional.
type PartialUser = Partial<User>; // { id?: number; name?: string; ... }

// 2. Readonly<T>: Makes all properties readonly.
const user: Readonly<User> = { id: 1, name: "John", email: "a@b.com" };
// user.name = "Jane"; // Error

// 3. Pick<T, K>: Creates a type with only the specified properties.
type UserPreview = Pick<User, "name" | "email">; // { name: string; email: string; }

// 4. Omit<T, K>: Creates a type with all properties except the specified ones.
type UserAuthData = Omit<User, "id">; // { name: string; email: string; }

Q25: What are Conditional Types?

Question: Explain conditional types in TypeScript.

Answer: Conditional types select a type based on a condition, using a ternary-like syntax: SomeType extends OtherType ? TrueType : FalseType;. They are fundamental to creating advanced, intelligent types.

Example (Built-in NonNullable<T>):

// If T can be null or undefined, the type is `never`, otherwise it's T.
type NonNullable<T> = T extends null | undefined ? never : T;

type ID = string | null | undefined;
type ConcreteID = NonNullable<ID>; // Type is `string`

Q26: What is the keyof operator?

Question: Explain the keyof type operator.

Answer: The keyof operator takes an object type and produces a string or numeric literal union of its keys.

interface Point {
  x: number;
  y: number;
}
// P is the type "x" | "y"
type P = keyof Point;

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}

Q27: unknown vs any?

Question: What is the difference between unknown and any?

Answer: Both can hold any value, but unknown is the type-safe counterpart to any.

let val: unknown = "hello";
// val.toUpperCase(); // Error: 'val' is of type 'unknown'.
if (typeof val === 'string') {
    console.log(val.toUpperCase()); // OK, val is narrowed to string
}

2.4 Decorators and Classes

Q28: What are Decorators?

Question: Explain decorators and provide an example. (Note: Experimental feature).

Answer: Decorators are special declarations (@expression) that can be attached to classes, methods, properties, or parameters to modify or observe them. They are functions that run at declaration time. Requires "experimentalDecorators": true in tsconfig.json.

Example (Method Decorator):

function LogMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function(...args: any[]) {
    console.log(`Calling "${propertyKey}" with`, args);
    return originalMethod.apply(this, args);
  };
}

class Person {
  @LogMethod
  greet(name: string) {
    return `Hello, ${name}!`;
  }
}
new Person().greet("World"); // Logs: Calling "greet" with ["World"]

Part 3: Data Structures & Algorithms

3.1 Array Challenges

Q29: Two Sum

Question: Given an array of integers nums and a target, return indices of the two numbers that add up to target.

Solution (Hash Map):

function twoSum(nums: number[], target: number): number[] {
  const map = new Map<number, number>(); // Stores {number: index}
  for (let i = 0; i < nums.length; i++) {
    const complement = target - nums[i];
    if (map.has(complement)) {
      return [map.get(complement)!, i];
    }
    map.set(nums[i], i);
  }
  return [];
}

Complexity: Time: O(n), Space: O(n).


Q30: Merge Sorted Arrays

Question: Merge nums2 into nums1 in-place. nums1 has enough space at the end.

Solution (Two Pointers from End):

function merge(nums1: number[], m: number, nums2: number[], n: number): void {
  let p1 = m - 1, p2 = n - 1, p = m + n - 1;
  while (p2 >= 0) {
    if (p1 >= 0 && nums1[p1] > nums2[p2]) {
      nums1[p--] = nums1[p1--];
    } else {
      nums1[p--] = nums2[p2--];
    }
  }
}

Complexity: Time: O(m + n), Space: O(1).


Q31: Maximum Subarray (Kadane’s Algorithm)

Question: Find the contiguous subarray with the largest sum.

Solution (Kadane’s Algorithm):

function maxSubArray(nums: number[]): number {
    let maxCurrent = nums[0];
    let maxGlobal = nums[0];
    for (let i = 1; i < nums.length; i++) {
        maxCurrent = Math.max(nums[i], maxCurrent + nums[i]);
        if (maxCurrent > maxGlobal) {
            maxGlobal = maxCurrent;
        }
    }
    return maxGlobal;
}

Complexity: Time: O(n), Space: O(1).


Q32: Rotate Array

Question: Rotate an array to the right by k steps.

Solution (Using Reverse):

function rotate(nums: number[], k: number): void {
    k %= nums.length;
    const reverse = (arr: number[], start: number, end: number) => {
        while (start < end) {
            [arr[start], arr[end]] = [arr[end], arr[start]];
            start++;
            end--;
        }
    };
    reverse(nums, 0, nums.length - 1); // Reverse the whole array
    reverse(nums, 0, k - 1);          // Reverse the first k elements
    reverse(nums, k, nums.length - 1); // Reverse the remaining elements
}

Complexity: Time: O(n), Space: O(1).


Q33: Contains Duplicate

Question: Determine if an array contains any duplicate values.

Solution (Using a Set):

function containsDuplicate(nums: number[]): boolean {
    const s = new Set(nums);
    return s.size !== nums.length;
}

Complexity: Time: O(n), Space: O(n).


Q34: Best Time to Buy and Sell Stock

Question: Find the maximum profit from a single buy and sell.

Solution (One Pass):

function maxProfit(prices: number[]): number {
    let minPrice = Infinity;
    let maxProfit = 0;
    for (const price of prices) {
        if (price < minPrice) {
            minPrice = price;
        } else if (price - minPrice > maxProfit) {
            maxProfit = price - minPrice;
        }
    }
    return maxProfit;
}

Complexity: Time: O(n), Space: O(1).


Q35: Product of Array Except Self

Question: Return an array where answer[i] is the product of all elements of nums except nums[i]. Do not use division.

Solution (Prefix and Postfix Products):

function productExceptSelf(nums: number[]): number[] {
    const n = nums.length;
    const answer = new Array(n).fill(1);
    
    let prefix = 1;
    for (let i = 0; i < n; i++) {
        answer[i] = prefix;
        prefix *= nums[i];
    }
    
    let postfix = 1;
    for (let i = n - 1; i >= 0; i--) {
        answer[i] *= postfix;
        postfix *= nums[i];
    }
    
    return answer;
}

Complexity: Time: O(n), Space: O(1) (excluding result array).


Q36: 3Sum

Question: Find all unique triplets in the array which give the sum of zero.

Solution (Sort and Two Pointers):

function threeSum(nums: number[]): number[][] {
    nums.sort((a, b) => a - b);
    const result: number[][] = [];
    
    for (let i = 0; i < nums.length - 2; i++) {
        if (i > 0 && nums[i] === nums[i - 1]) continue; // Skip duplicates
        let left = i + 1;
        let right = nums.length - 1;
        while (left < right) {
            const sum = nums[i] + nums[left] + nums[right];
            if (sum === 0) {
                result.push([nums[i], nums[left], nums[right]]);
                while (left < right && nums[left] === nums[left + 1]) left++; // Skip duplicates
                while (left < right && nums[right] === nums[right - 1]) right--; // Skip duplicates
                left++;
                right--;
            } else if (sum < 0) {
                left++;
            } else {
                right--;
            }
        }
    }
    return result;
}

Complexity: Time: O(n^2), Space: O(1) or O(n) depending on sort implementation.


Q37: Container With Most Water

Question: Find two lines that together with the x-axis form a container, such that the container contains the most water.

Solution (Two Pointers):

function maxArea(height: number[]): number {
    let max = 0;
    let left = 0;
    let right = height.length - 1;
    while (left < right) {
        const h = Math.min(height[left], height[right]);
        const w = right - left;
        max = Math.max(max, h * w);
        if (height[left] < height[right]) {
            left++;
        } else {
            right--;
        }
    }
    return max;
}

Complexity: Time: O(n), Space: O(1).


Q38: Flatten a Nested Array

Question: Write a function to flatten a deeply nested array.

Solution (Recursive):

function flatten(arr) {
    let result = [];
    for (const item of arr) {
        if (Array.isArray(item)) {
            result = result.concat(flatten(item));
        } else {
            result.push(item);
        }
    }
    return result;
}

// Modern ES2019+ solution
// const flatDeep = (arr) => arr.flat(Infinity);

console.log(flatten([1, [2, [3, 4], 5], 6])); // [1, 2, 3, 4, 5, 6]

Complexity: Time: O(N) where N is total elements, Space: O(D) where D is max depth.


3.2 String Challenges

Q39: Valid Palindrome

Question: Check if a string is a palindrome, considering only alphanumeric characters and ignoring cases.

Solution (Two Pointers):

function isPalindrome(s: string): boolean {
    const cleaned = s.toLowerCase().replace(/[^a-z0-9]/g, '');
    let left = 0, right = cleaned.length - 1;
    while (left < right) {
        if (cleaned[left] !== cleaned[right]) return false;
        left++;
        right--;
    }
    return true;
}

Complexity: Time: O(n), Space: O(n) for cleaned string.


Q40: Valid Anagram

Question: Check if string t is an anagram of string s.

Solution (Character Map):

function isAnagram(s: string, t: string): boolean {
    if (s.length !== t.length) return false;
    const charMap = new Map<string, number>();
    for (const char of s) {
        charMap.set(char, (charMap.get(char) || 0) + 1);
    }
    for (const char of t) {
        if (!charMap.has(char) || charMap.get(char) === 0) return false;
        charMap.set(char, charMap.get(char)! - 1);
    }
    return true;
}

Complexity: Time: O(n), Space: O(k) where k is number of unique chars.


Q41: Longest Substring Without Repeating Characters

Question: Find the length of the longest substring without repeating characters.

Solution (Sliding Window with a Map):

function lengthOfLongestSubstring(s: string): number {
    let maxLength = 0;
    let start = 0;
    const map = new Map<string, number>(); // char -> last seen index
    
    for (let end = 0; end < s.length; end++) {
        const char = s[end];
        if (map.has(char) && map.get(char)! >= start) {
            start = map.get(char)! + 1;
        }
        map.set(char, end);
        maxLength = Math.max(maxLength, end - start + 1);
    }
    return maxLength;
}

Complexity: Time: O(n), Space: O(k) where k is number of unique chars.


Q42: String Compression

Question: Implement basic string compression, e.g., “aabcccccaaa” -> “a2b1c5a3”.

Solution (Iteration):

function compressString(str) {
    if (str.length === 0) return "";
    let compressed = "";
    let count = 1;
    for (let i = 0; i < str.length; i++) {
        if (str[i] === str[i + 1]) {
            count++;
        } else {
            compressed += str[i] + count;
            count = 1;
        }
    }
    return compressed.length < str.length ? compressed : str;
}

Complexity: Time: O(n), Space: O(n).


Q43: Valid Parentheses

Question: Given a string containing just (), {}, [], determine if it’s valid.

Solution (Stack):

function isValid(s: string): boolean {
    const stack: string[] = [];
    const map: { [key: string]: string } = { "(": ")", "{": "}", "[": "]" };
    for (const char of s) {
        if (map[char]) {
            stack.push(char);
        } else {
            const lastOpen = stack.pop();
            if (map[lastOpen!] !== char) return false;
        }
    }
    return stack.length === 0;
}

Complexity: Time: O(n), Space: O(n).


Q44: Group Anagrams

Question: Group an array of strings by anagrams.

Solution (Map with Sorted Strings):

function groupAnagrams(strs: string[]): string[][] {
    const map = new Map<string, string[]>();
    for (const str of strs) {
        const sortedStr = str.split('').sort().join('');
        if (!map.has(sortedStr)) {
            map.set(sortedStr, []);
        }
        map.get(sortedStr)!.push(str);
    }
    return Array.from(map.values());
}

Complexity: Time: O(N * K log K) where N is number of strings, K is max length. Space: O(N * K).


Q45: Implement strStr()

Question: Find the first occurrence of a needle in a haystack.

Solution (Built-in or Simple Loop):

function strStr(haystack: string, needle: string): number {
    if (needle.length === 0) return 0;
    for (let i = 0; i <= haystack.length - needle.length; i++) {
        if (haystack.substring(i, i + needle.length) === needle) {
            return i;
        }
    }
    return -1;
}
// return haystack.indexOf(needle); // The easy way

Complexity: Time: O(m * n), Space: O(1).


3.3 Object and Map Challenges

Q46: Deep Clone an Object

Question: Write a function to deeply clone an object, handling nested objects, arrays, and cycles.

Solution (Recursive with a Map for Cycles):

function deepClone(obj, map = new WeakMap()) {
    if (obj === null || typeof obj !== 'object') {
        return obj;
    }
    // Handle cycles
    if (map.has(obj)) {
        return map.get(obj);
    }
    
    const clone = Array.isArray(obj) ? [] : {};
    map.set(obj, clone);
    
    for (const key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
            clone[key] = deepClone(obj[key], map);
        }
    }
    return clone;
}

Complexity: Time: O(N) where N is nodes/properties, Space: O(N).


Q47: Invert a Key-Value Object

Question: Invert an object’s keys and values.

Solution (Iteration):

function invertObject(obj) {
    const inverted = {};
    for (const key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
            inverted[obj[key]] = key;
        }
    }
    return inverted;
}

Complexity: Time: O(n), Space: O(n).


3.4 Recursion and Dynamic Programming

Q48: Fibonacci Sequence (with DP)

Question: Calculate the nth Fibonacci number efficiently.

Solution (Dynamic Programming - Tabulation):

function fib(n: number): number {
    if (n <= 1) return n;
    const dp = [0, 1];
    for (let i = 2; i <= n; i++) {
        dp[i] = dp[i - 1] + dp[i - 2];
    }
    return dp[n];
}

Complexity: Time: O(n), Space: O(n). (Can be optimized to O(1) space).


Q49: Climbing Stairs

Question: You can climb 1 or 2 steps at a time. How many distinct ways can you climb to the top of n stairs?

Solution (Dynamic Programming): This is a Fibonacci problem in disguise.

function climbStairs(n: number): number {
    if (n <= 2) return n;
    let oneStepBefore = 2;
    let twoStepsBefore = 1;
    let allWays = 0;
    for (let i = 3; i <= n; i++) {
        allWays = oneStepBefore + twoStepsBefore;
        twoStepsBefore = oneStepBefore;
        oneStepBefore = allWays;
    }
    return allWays;
}

Complexity: Time: O(n), Space: O(1).


Q50: Coin Change

Question: Find the fewest number of coins to make up an amount.

Solution (Dynamic Programming):

function coinChange(coins: number[], amount: number): number {
    const dp = new Array(amount + 1).fill(Infinity);
    dp[0] = 0;
    
    for (let i = 1; i <= amount; i++) {
        for (const coin of coins) {
            if (i - coin >= 0) {
                dp[i] = Math.min(dp[i], dp[i - coin] + 1);
            }
        }
    }
    
    return dp[amount] === Infinity ? -1 : dp[amount];
}

Complexity: Time: O(amount * coins.length), Space: O(amount).


Q51: Generate All Subsets (Power Set)

Question: Return all possible subsets of a given set of distinct integers.

Solution (Backtracking/Recursion):

function subsets(nums: number[]): number[][] {
    const result: number[][] = [];
    
    function backtrack(index: number, currentSubset: number[]) {
        result.push([...currentSubset]);
        for (let i = index; i < nums.length; i++) {
            currentSubset.push(nums[i]);
            backtrack(i + 1, currentSubset);
            currentSubset.pop();
        }
    }
    
    backtrack(0, []);
    return result;
}

Complexity: Time: O(N * 2^N), Space: O(N * 2^N).


Q52: Word Break

Question: Determine if a string s can be segmented into a space-separated sequence of one or more dictionary words.

Solution (Dynamic Programming):

function wordBreak(s: string, wordDict: string[]): boolean {
    const wordSet = new Set(wordDict);
    const dp = new Array(s.length + 1).fill(false);
    dp[0] = true;
    
    for (let i = 1; i <= s.length; i++) {
        for (let j = 0; j < i; j++) {
            if (dp[j] && wordSet.has(s.substring(j, i))) {
                dp[i] = true;
                break;
            }
        }
    }
    return dp[s.length];
}

Complexity: Time: O(n^2), Space: O(n).


3.5 Stacks and Queues

Q53: Implement a Queue using two Stacks.

Question: Implement enqueue and dequeue for a Queue using only Stack instances.

Solution:

class QueueWithStacks {
    constructor() {
        this.inStack = [];
        this.outStack = [];
    }

    enqueue(val) {
        this.inStack.push(val);
    }

    dequeue() {
        if (this.outStack.length === 0) {
            while (this.inStack.length > 0) {
                this.outStack.push(this.inStack.pop());
            }
        }
        return this.outStack.pop();
    }
    
    peek() {
        if (this.outStack.length === 0) {
            while (this.inStack.length > 0) {
                this.outStack.push(this.inStack.pop());
            }
        }
        return this.outStack[this.outStack.length - 1];
    }
}

Complexity: Amortized O(1) for both operations.


Q54: Min Stack

Question: Design a stack that supports push, pop, top, and getMin in constant time.

Solution (Two Stacks):

class MinStack {
    private stack: number[] = [];
    private minStack: number[] = [];

    push(val: number): void {
        this.stack.push(val);
        const min = this.minStack.length > 0 ? this.minStack[this.minStack.length - 1] : Infinity;
        this.minStack.push(Math.min(min, val));
    }

    pop(): void {
        this.stack.pop();
        this.minStack.pop();
    }

    top(): number {
        return this.stack[this.stack.length - 1];
    }

    getMin(): number {
        return this.minStack[this.minStack.length - 1];
    }
}

Complexity: Time: O(1) for all operations, Space: O(n).


3.6 Linked Lists

Q55: Reverse a Linked List

Question: Reverse a singly linked list.

Solution (Iterative):

class ListNode {
    val: number;
    next: ListNode | null;
    constructor(val = 0, next = null) { this.val = val; this.next = next; }
}

function reverseList(head: ListNode | null): ListNode | null {
    let prev = null;
    let curr = head;
    while (curr) {
        const nextTemp = curr.next;
        curr.next = prev;
        prev = curr;
        curr = nextTemp;
    }
    return prev;
}

Complexity: Time: O(n), Space: O(1).


Q56: Detect a Cycle in a Linked List

Question: Determine if a linked list has a cycle.

Solution (Floyd’s Tortoise and Hare Algorithm):

function hasCycle(head: ListNode | null): boolean {
    let slow = head, fast = head;
    while (fast && fast.next) {
        slow = slow!.next;
        fast = fast.next.next;
        if (slow === fast) return true;
    }
    return false;
}

Complexity: Time: O(n), Space: O(1).


Q57: Merge Two Sorted Lists

Question: Merge two sorted linked lists into one sorted list.

Solution (Iterative with Dummy Node):

function mergeTwoLists(l1: ListNode | null, l2: ListNode | null): ListNode | null {
    const dummy = new ListNode();
    let tail = dummy;
    while (l1 && l2) {
        if (l1.val < l2.val) {
            tail.next = l1;
            l1 = l1.next;
        } else {
            tail.next = l2;
            l2 = l2.next;
        }
        tail = tail.next;
    }
    tail.next = l1 || l2;
    return dummy.next;
}

Complexity: Time: O(m + n), Space: O(1).


3.7 Trees and Graphs

Q58: Maximum Depth of a Binary Tree

Question: Find the maximum depth (height) of a binary tree.

Solution (Recursive):

class TreeNode {
    val: number;
    left: TreeNode | null;
    right: TreeNode | null;
    constructor(val = 0, left = null, right = null) { this.val = val; this.left = left; this.right = right; }
}

function maxDepth(root: TreeNode | null): number {
    if (!root) return 0;
    return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
}

Complexity: Time: O(n), Space: O(h) where h is height.


Q59: Invert a Binary Tree

Question: Invert a binary tree.

Solution (Recursive):

function invertTree(root: TreeNode | null): TreeNode | null {
    if (!root) return null;
    [root.left, root.right] = [invertTree(root.right), invertTree(root.left)];
    return root;
}

Complexity: Time: O(n), Space: O(h).


Q60: Validate Binary Search Tree

Question: Determine if a binary tree is a valid Binary Search Tree (BST).

Solution (Recursive with Range):

function isValidBST(root: TreeNode | null, min = -Infinity, max = Infinity): boolean {
    if (!root) return true;
    if (root.val <= min || root.val >= max) return false;
    return isValidBST(root.left, min, root.val) && isValidBST(root.right, root.val, max);
}

Complexity: Time: O(n), Space: O(h).


Q61: Binary Tree Level Order Traversal

Question: Traverse a binary tree level by level.

Solution (BFS with a Queue):

function levelOrder(root: TreeNode | null): number[][] {
    if (!root) return [];
    const result: number[][] = [];
    const queue: TreeNode[] = [root];
    
    while (queue.length > 0) {
        const levelSize = queue.length;
        const currentLevel: number[] = [];
        for (let i = 0; i < levelSize; i++) {
            const node = queue.shift()!;
            currentLevel.push(node.val);
            if (node.left) queue.push(node.left);
            if (node.right) queue.push(node.right);
        }
        result.push(currentLevel);
    }
    return result;
}

Complexity: Time: O(n), Space: O(w) where w is max width.


Q62: Number of Islands (Graph Traversal)

Question: Given a 2D grid of ‘1’s (land) and ‘0’s (water), count the number of islands.

Solution (DFS):

function numIslands(grid: string[][]): number {
    if (!grid || grid.length === 0) return 0;
    let count = 0;
    
    function dfs(r: number, c: number) {
        if (r < 0 || r >= grid.length || c < 0 || c >= grid[0].length || grid[r][c] === '0') {
            return;
        }
        grid[r][c] = '0'; // Mark as visited
        dfs(r + 1, c);
        dfs(r - 1, c);
        dfs(r, c + 1);
        dfs(r, c - 1);
    }
    
    for (let r = 0; r < grid.length; r++) {
        for (let c = 0; c < grid[0].length; c++) {
            if (grid[r][c] === '1') {
                count++;
                dfs(r, c);
            }
        }
    }
    return count;
}

Complexity: Time: O(MN), Space: O(MN) in worst case for recursion stack.


3.8 Sorting and Searching

Question: Write a function to perform a binary search on a sorted array.

Solution:

function search(nums: number[], target: number): number {
    let left = 0, right = nums.length - 1;
    while (left <= right) {
        const mid = Math.floor(left + (right - left) / 2);
        if (nums[mid] === target) {
            return mid;
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    return -1;
}

Complexity: Time: O(log n), Space: O(1).


Q64: Implement Quick Sort

Question: Write a function to sort an array using the Quick Sort algorithm.

Solution:

function quickSort(arr) {
    if (arr.length <= 1) return arr;
    
    const pivot = arr[arr.length - 1];
    const left = [];
    const right = [];
    
    for (let i = 0; i < arr.length - 1; i++) {
        if (arr[i] < pivot) {
            left.push(arr[i]);
        } else {
            right.push(arr[i]);
        }
    }
    
    return [...quickSort(left), pivot, ...quickSort(right)];
}

Complexity: Average Time: O(n log n), Worst Time: O(n^2), Space: O(log n).


Q65: Implement Merge Sort

Question: Write a function to sort an array using the Merge Sort algorithm.

Solution:

function mergeSort(arr) {
    if (arr.length <= 1) return arr;
    
    const mid = Math.floor(arr.length / 2);
    const left = mergeSort(arr.slice(0, mid));
    const right = mergeSort(arr.slice(mid));
    
    return merge(left, right);
}

function merge(left, right) {
    const result = [];
    let i = 0, j = 0;
    while (i < left.length && j < right.length) {
        if (left[i] < right[j]) {
            result.push(left[i++]);
        } else {
            result.push(right[j++]);
        }
    }
    return result.concat(left.slice(i)).concat(right.slice(j));
}

Complexity: Time: O(n log n), Space: O(n).


Part 4: Assorted Conceptual Questions

Q66: == vs ===?

Q67: What is a “pure function”? A function that, given the same input, will always return the same output and has no observable side effects (e.g., modifying external state, logging to console).

Q68: What are the ways to create an object in JavaScript?

Q69: Object.freeze() vs Object.seal()?

Q70: What is the arguments object? An array-like object accessible inside regular functions that contains the values of the arguments passed to that function. It’s not a true array. Arrow functions do not have their own arguments object. Prefer using rest parameters (...args).

Q71: JSON.stringify vs JSON.parse?

Q72: Explain the Same-origin policy and CORS.

Q73: What is tree-shaking? A dead-code elimination process used by modern JavaScript bundlers (like Webpack or Rollup). It analyzes your code and dependencies and removes any unused code, resulting in smaller bundle sizes.

Q74: ?. (optional chaining) vs ?? (nullish coalescing)?

Of course. Here is a comprehensive, detailed explanation for the instanceof operator, as requested.


Q75: What is the instanceof operator?

Question: Explain in detail what the instanceof operator is, how it works under the hood, and discuss its common use cases and potential pitfalls.

Answer:

The instanceof operator in JavaScript is a binary operator that tests whether the prototype property of a constructor function appears anywhere in the prototype chain of an object. It provides a way to determine an object’s type based on its constructor heritage.

The syntax is:

object instanceof Constructor

The operator returns true if the object is an instance of the constructor or an instance of a class that inherits from the constructor. Otherwise, it returns false.

How It Works Internally

When you execute object instanceof Constructor, the JavaScript engine performs the following check:

  1. It gets the prototype of the object (i.e., Object.getPrototypeOf(object)).
  2. It compares this prototype to Constructor.prototype.
  3. If they are a strict match (===), it returns true.
  4. If they are not a match, it follows the prototype chain up from the object’s prototype. It gets the prototype of the prototype (Object.getPrototypeOf(Object.getPrototypeOf(object))) and repeats the comparison.
  5. This process continues until it either finds a match (returning true) or it reaches the end of the prototype chain (where the prototype is null). If the end of the chain is reached without a match, it returns false.

Basic Code Example

Let’s illustrate this with a classic inheritance example.

// Base constructor function
function Animal(name) {
  this.name = name;
}
Animal.prototype.speak = function() {
  console.log(`${this.name} makes a noise.`);
};

// Child constructor function inheriting from Animal
function Dog(name, breed) {
  Animal.call(this, name); // Call super constructor
  this.breed = breed;
}
// Set up the prototype chain: Dog.prototype -> Animal.prototype -> Object.prototype -> null
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // Reset the constructor property

const myDog = new Dog('Rex', 'German Shepherd');

// --- Let's run the checks ---

// 1. Is myDog an instance of Dog?
//    - Object.getPrototypeOf(myDog) === Dog.prototype  -> TRUE
console.log(myDog instanceof Dog); // true

// 2. Is myDog an instance of Animal?
//    - Object.getPrototypeOf(myDog) === Animal.prototype -> FALSE
//    - Object.getPrototypeOf(Object.getPrototypeOf(myDog)) === Animal.prototype -> TRUE
console.log(myDog instanceof Animal); // true

// 3. Is myDog an instance of Object?
//    - The chain continues up to Object.prototype
console.log(myDog instanceof Object); // true

// 4. Is myDog an instance of Array?
//    - Array.prototype is not in myDog's prototype chain.
console.log(myDog instanceof Array); // false

// An instance of the base class is not an instance of the child class.
const genericAnimal = new Animal('Generic');
console.log(genericAnimal instanceof Dog); // false

Key Nuances and Edge Cases

1. Primitives

The instanceof operator works on objects. It returns false for primitive values (string, number, boolean, symbol, bigint, undefined, null).

console.log('hello' instanceof String); // false
console.log(123 instanceof Number);     // false
console.log(false instanceof Boolean);  // false

// However, it returns true for objects created with a constructor (wrapper objects)
const strObj = new String('hello');
console.log(strObj instanceof String); // true

2. Multiple Realms (A Critical Pitfall)

A “realm” in JavaScript is a distinct global environment (e.g., a window, an iframe, or a Web Worker). Each realm has its own set of global objects and its own set of built-in type constructors (Array, Object, etc.).

If an object is created in one realm and tested against a constructor from another realm, instanceof will fail, even if the object is structurally identical. This is because their prototypes are different objects in memory.

This is the most common interview gotcha related to instanceof.

// --- Fictional example to illustrate the concept ---

// Imagine this code runs inside an iframe
// const arrayFromIframe = new Array(1, 2, 3);

// Now, in the main window's script:
// console.log(arrayFromIframe instanceof Array); // FALSE!

// Why? Because `Object.getPrototypeOf(arrayFromIframe)` points to `iframe.contentWindow.Array.prototype`,
// which is a different object than the main window's `window.Array.prototype`.

// This is why `Array.isArray()` was created. It reliably checks if something is an array,
// regardless of its realm of origin.
// Array.isArray(arrayFromIframe); // TRUE

3. Overriding Behavior with Symbol.hasInstance

ES6 introduced the Symbol.hasInstance well-known symbol. A class or constructor function can define a static method with this symbol as its key to completely override the default behavior of instanceof.

class SpecialCheck {
  static [Symbol.hasInstance](instance) {
    // Custom logic: check if the instance has a specific property
    if (instance && instance.isSpecial) {
      return true;
    }
    return false;
  }
}

const obj1 = { isSpecial: true };
const obj2 = { isSpecial: false };
const obj3 = {};

console.log(obj1 instanceof SpecialCheck); // true
console.log(obj2 instanceof SpecialCheck); // false
console.log(obj3 instanceof SpecialCheck); // false

Summary and Best Practices


Q76: Web Workers

Question: What are Web Workers and how do you use them? Provide a code example.

Answer: Web Workers are a simple means for web content to run scripts in a background thread. The worker thread can perform tasks without interfering with the user interface, preventing the main page from becoming unresponsive. The main script and the worker communicate using a system of messages—both sides send messages with postMessage() and respond to messages via the onmessage event handler. Workers cannot directly access the DOM.

Code Example: This example demonstrates a worker performing a heavy calculation (finding prime numbers) without freezing the UI.

index.html (Main Page)

<!DOCTYPE html>
<html>
<head>
    <title>Web Worker Demo</title>
</head>
<body>
    <h1>Web Worker Demo</h1>
    <p>Click the button to calculate primes up to 10,000,000 without freezing the page.</p>
    <button id="start-worker">Start Calculation</button>
    <button id="update-counter">Click Me to Prove UI is Not Frozen</button>
    <p>Counter: <span id="counter">0</span></p>
    <h2>Result from Worker:</h2>
    <div id="result"></div>
    <script src="main.js"></script>
</body>
</html>

main.js (Main Script)

const startButton = document.getElementById('start-worker');
const resultDiv = document.getElementById('result');
const counterButton = document.getElementById('update-counter');
const counterSpan = document.getElementById('counter');

let count = 0;

if (window.Worker) {
    const myWorker = new Worker('worker.js');

    startButton.onclick = function() {
        resultDiv.textContent = 'Calculating...';
        // Send a message to the worker to start the task
        myWorker.postMessage(10000000); 
    };

    // Listen for messages from the worker
    myWorker.onmessage = function(e) {
        resultDiv.textContent = `Found ${e.data} primes.`;
        console.log('Message received from worker');
    };

    myWorker.onerror = function(e) {
        console.error('Error in worker:', e);
    };

} else {
    console.log('Your browser doesn\'t support Web Workers.');
}

// This button proves the UI is still responsive
counterButton.onclick = function() {
    count++;
    counterSpan.textContent = count;
};

worker.js (Worker Script)

// Listen for messages from the main script
self.onmessage = function(e) {
    console.log('Message received in worker. Starting calculation...');
    const upperLimit = e.data;
    
    // Heavy calculation
    function isPrime(num) {
        for (let i = 2, s = Math.sqrt(num); i <= s; i++)
            if (num % i === 0) return false;
        return num > 1;
    }

    let primeCount = 0;
    for (let i = 0; i < upperLimit; i++) {
        if (isPrime(i)) {
            primeCount++;
        }
    }

    // Send the result back to the main script
    self.postMessage(primeCount);
};

Q77: Polyfills

Question: What is a polyfill? Provide an example of a polyfill for a modern JavaScript feature.

Answer: A polyfill is a piece of code (usually JavaScript on the Web) used to provide modern functionality on older browsers that do not natively support it. It “fills in” the gap, allowing developers to use modern APIs without worrying about browser compatibility.

Code Example (Polyfill for Array.prototype.flat) The .flat() method creates a new array with all sub-array elements concatenated into it recursively up to the specified depth.

// The polyfill checks if the method exists before defining it.
if (!Array.prototype.flat) {
    Object.defineProperty(Array.prototype, 'flat', {
        configurable: true,
        writable: true,
        value: function(depth = 1) {
            // Ensure the context is an object, as per spec.
            if (this == null) {
                throw new TypeError("Array.prototype.flat called on null or undefined");
            }

            const stack = [...this];
            const result = [];
            
            while (stack.length > 0) {
                const item = stack.shift();
                
                if (Array.isArray(item) && depth > 0) {
                    // Push the flattened items to the front of the stack
                    stack.unshift(...item);
                    // Only decrement depth for the whole array level
                    // This implementation simplifies depth handling by re-flattening
                    // A more accurate polyfill would track depth per element.
                } else {
                    result.push(item);
                }
            }
            // A simplified polyfill, a full one would handle depth more precisely.
            // For depth > 1, you'd recursively call or adjust the loop.
            // This basic version effectively does a depth of 1.
            return result; 
        }
    });
}


// --- Usage Example ---
const nestedArray = [1, 2, [3, 4, [5, 6]]];

// Using the polyfilled method
const flattenedOnce = nestedArray.flat(); 
console.log(flattenedOnce); // Output: [1, 2, 3, 4, [5, 6]]

// Note: A true polyfill for depth > 1 is more complex. For example:
// const flattenedTwice = nestedArray.flat(2); 
// console.log(flattenedTwice); // would be [1, 2, 3, 4, 5, 6]

Q78: Mixins (TypeScript)

Question: What are mixins in TypeScript? Provide a code example of how to implement them.

Answer: Mixins are a pattern for class composition. Instead of inheriting from a single superclass, a class can be composed of multiple smaller, reusable pieces of functionality called mixins. TypeScript implements this by having a class implement multiple interfaces and then using a helper function to merge the prototypes of the mixin classes into the target class.

Code Example:

// Mixin 1: Adds a timestamp property
class Timestamped {
    timestamp: Date = new Date();
}

// Mixin 2: Adds an active property and toggle method
class Activatable {
    isActive: boolean = false;
    activate() { this.isActive = true; }
    deactivate() { this.isActive = false; }
}

// Base Class
class User {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
}

// Create a new class that will be composed of the User class and the mixins.
// It must implement the interfaces of all parts to satisfy the type checker.
interface SmartUser extends User, Timestamped, Activatable {}
class SmartUser {
    constructor(name: string) {
        // The base class constructor is implicitly called by the runtime.
        // We can call it explicitly if needed.
        // super(name); // not needed here as User constructor is simple.
        this.name = name;
    }
}

// Helper function to apply mixins to a class
function applyMixins(derivedCtor: any, baseCtors: any[]) {
    baseCtors.forEach(baseCtor => {
        Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
            Object.defineProperty(derivedCtor.prototype, name, Object.getOwnPropertyDescriptor(baseCtor.prototype, name)!);
        });
    });
}

// Apply the mixins to our SmartUser class
applyMixins(SmartUser, [Timestamped, Activatable]);

// --- Usage ---
const smartUser = new SmartUser("Alice");

console.log(smartUser.name); // "Alice"

// From Timestamped mixin
console.log(smartUser.timestamp); // Logs the creation date/time

// From Activatable mixin
console.log(smartUser.isActive); // false
smartUser.activate();
console.log(smartUser.isActive); // true

Q79: The bind method

Question: Explain what Function.prototype.bind does and provide a polyfill for it.

Answer: The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called. It essentially “locks” the this context of a function.

Code Example (Polyfill for bind)

// --- Problem demonstration ---
const module = {
  x: 42,
  getX: function() {
    return this.x;
  }
};

const unboundGetX = module.getX;
// console.log(unboundGetX()); // Throws TypeError in strict mode, or returns `undefined` (window.x)

const boundGetX = unboundGetX.bind(module);
console.log(boundGetX()); // 42

// --- Polyfill for bind ---
if (!Function.prototype.bind) {
  Function.prototype.bind = function(oThis) {
    if (typeof this !== 'function') {
      throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
    }

    // Capture the arguments passed to bind (after the 'this' context)
    const aArgs = Array.prototype.slice.call(arguments, 1);
    const fToBind = this; // The original function

    const fNOP = function() {}; // A no-op constructor

    const fBound = function() {
      // The `this` for the new function call, and its arguments
      const context = this instanceof fNOP ? this : oThis;
      const finalArgs = aArgs.concat(Array.prototype.slice.call(arguments));
      return fToBind.apply(context, finalArgs);
    };
    
    // Maintain the prototype chain
    if (this.prototype) {
        fNOP.prototype = this.prototype;
    }
    fBound.prototype = new fNOP();

    return fBound;
  };
}

Q80: Event Bubbling vs Capturing

Question: Explain event bubbling and capturing, and show how to use them with addEventListener.

Answer: When an event occurs on an element, it goes through two phases:

  1. Capturing Phase: The event travels from the root of the document (window) down to the target element. Listeners registered for the capture phase are triggered during this descent.
  2. Bubbling Phase: After reaching the target, the event travels up from the target element back to the root. This is the default behavior for most events.

You control which phase to listen for with the third argument of addEventListener(event, handler, useCapture).

Code Example:

<div id="grandparent">
  <div id="parent">
    <div id="child">Click Me</div>
  </div>
</div>
<script>
  const gp = document.getElementById('grandparent');
  const p = document.getElementById('parent');
  const c = document.getElementById('child');

  // Add listeners for the CAPTURING phase (true)
  gp.addEventListener('click', () => console.log('Grandparent Capture'), true);
  p.addEventListener('click', () => console.log('Parent Capture'), true);
  c.addEventListener('click', () => console.log('Child Capture'), true);

  // Add listeners for the BUBBLING phase (false or default)
  gp.addEventListener('click', () => console.log('Grandparent Bubble'), false);
  p.addEventListener('click', () => console.log('Parent Bubble'), false);
  c.addEventListener('click', () => console.log('Child Bubble'), false);

  /*
  When you click the "child" div, the console output will be:
  
  Grandparent Capture  // Event travels down
  Parent Capture       // Event travels down
  Child Capture        // Event reaches target (capture phase listener)
  Child Bubble         // Event at target (bubble phase listener)
  Parent Bubble        // Event travels up
  Grandparent Bubble   // Event travels up
  */
</script>

Q81-83: Map, Set, WeakMap, WeakSet, Symbol

These are modern JS features. The code examples demonstrate their core functionality.

Map and Set Objects (Q81)

// --- Set Example ---
const mySet = new Set();
mySet.add(1);          // Set(1) { 1 }
mySet.add(5);          // Set(2) { 1, 5 }
mySet.add(5);          // Still Set(2) { 1, 5 } because values must be unique
mySet.add("some text"); // Set(3) { 1, 5, 'some text' }
console.log(mySet.has(1)); // true

// --- Map Example ---
const myMap = new Map();
const keyString = 'a string';
const keyObj = {};
const keyFunc = function() {};

myMap.set(keyString, "value associated with 'a string'");
myMap.set(keyObj, 'value associated with keyObj');
myMap.set(keyFunc, 'value associated with keyFunc');

console.log(myMap.size); // 3
console.log(myMap.get(keyObj)); // "value associated with keyObj"

WeakMap and WeakSet (Q82) Their keys/values must be objects, and they hold “weak” references. This means if an object stored in a WeakMap/WeakSet is the only remaining reference to that object, the garbage collector is free to destroy it. They are not iterable.

// --- WeakMap Example ---
let john = { name: "John" };
const weakMap = new WeakMap();
weakMap.set(john, "secret data");

// Now, if we remove the only strong reference to `john`...
john = null;

// ...the garbage collector can remove the `john` object from memory,
// and the entry in the `weakMap` will also be removed automatically.
// This prevents memory leaks.
// We can't iterate or check the size, so we can't directly observe its removal.

Symbol Primitive Type (Q83) A Symbol is a unique and immutable primitive value and may be used as the key of an Object property. They are often used to create “private” properties on objects that won’t conflict with string keys.

const idSymbol = Symbol('id'); // The description 'id' is for debugging only.
const user = {
    name: 'Alex',
    [idSymbol]: 'xyz-123' // Using a symbol as a property key
};

console.log(user.name);      // 'Alex'
console.log(user[idSymbol]); // 'xyz-123'

// Symbols are not enumerated in for...in loops
for (let key in user) {
    console.log(key); // Only 'name' is logged
}

// They are also ignored by JSON.stringify
console.log(JSON.stringify(user)); // '{"name":"Alex"}'

// To get symbols, you must use Object.getOwnPropertySymbols
console.log(Object.getOwnPropertySymbols(user)); // [Symbol(id)]

Q84: Generator functions

Question: What are generator functions? Provide an example.

Answer: A generator function (function*) is a special function that can be paused and resumed, allowing it to produce a sequence of values over time. When called, it doesn’t run its code but returns a special Generator object. You use the yield keyword to pause the function and produce a value. The next() method on the generator object resumes execution.

Code Example:

// A generator function to create a sequence of IDs
function* idGenerator() {
    let id = 1;
    while (true) {
        yield id++;
    }
}

const gen = idGenerator(); // Get the generator object

console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3

// Generators are iterable, so you can use them in for...of loops
function* fibonacci() {
    let [prev, curr] = [0, 1];
    while (true) {
        yield prev;
        [prev, curr] = [curr, prev + curr];
    }
}

const fibGen = fibonacci();
for (let i = 0; i < 10; i++) {
    console.log(fibGen.next().value); // Logs the first 10 Fibonacci numbers
}

Q85 & 86: Proxy and Reflect API

Question: What are Proxy and Reflect? Provide an example of them working together.

Answer:

Code Example (Validation and Logging Proxy):

const user = {
    name: "John",
    age: 30,
    email: "john@example.com"
};

const handler = {
    get(target, property) {
        console.log(`Getting property "${property}"`);
        // Use Reflect to perform the default get operation
        return Reflect.get(target, property);
    },
    set(target, property, value) {
        console.log(`Setting property "${property}" to "${value}"`);
        if (property === 'age' && (typeof value !== 'number' || value <= 0)) {
            throw new TypeError("Age must be a positive number.");
        }
        if (property === 'email' && !value.includes('@')) {
            throw new TypeError("Invalid email format.");
        }
        // Use Reflect to perform the default set operation
        return Reflect.set(target, property, value);
    }
};

const userProxy = new Proxy(user, handler);

// --- Usage ---
console.log(userProxy.name); // Logs: Getting property "name" -> John

userProxy.age = 31; // Logs: Setting property "age" to "31"
console.log(userProxy.age); // Logs: Getting property "age" -> 31

try {
    userProxy.age = -5; // Throws TypeError
} catch (e) {
    console.error(e.message);
}

try {
    userProxy.email = "invalid-email"; // Throws TypeError
} catch (e) {
    console.error(e.message);
}

Q87 - Q96: Conceptual Questions with Code Snippets

Q87: Differences between DOM and Shadow DOM

Q92: What is JSX? JSX (JavaScript XML) is a syntax extension for JavaScript, popularized by React. It lets you write HTML-like markup inside a JavaScript file. This code is then transpiled by tools like Babel into standard React.createElement() calls.

// This is JSX
const element = <h1 className="greeting">Hello, world!</h1>;

// This is what it transpiles to
const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

Q94: Higher-Order Components (HOCs) in React A Higher-Order Component is an advanced React pattern. It’s a function that takes a component as an argument and returns a new component, usually with some enhanced functionality or additional props.

import React, { useState, useEffect } from 'react';

// This is the HOC
function withLoading(WrappedComponent) {
  // It returns a new component...
  return function ComponentWithLoading(props) {
    const [isLoading, setIsLoading] = useState(true);

    useEffect(() => {
      // Simulate a data fetch
      setTimeout(() => setIsLoading(false), 2000);
    }, []);

    if (isLoading) {
      return <div>Loading...</div>;
    }

    // Pass original props through to the wrapped component
    return <WrappedComponent {...props} />;
  };
}

// A simple component to be wrapped
function UserProfile({ name }) {
  return <h1>{name}'s Profile</h1>;
}

// Create the enhanced component using the HOC
const UserProfileWithLoading = withLoading(UserProfile);

// Usage in an app
// <UserProfileWithLoading name="Sarah" />
// This will show "Loading..." for 2 seconds, then show "Sarah's Profile".

Q96: let in loops vs var This revisits Q4. let creates a new binding for each loop iteration, whereas var declares a single variable for the entire function scope.

// `var` - all callbacks reference the SAME `i`
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(`var: ${i}`), 10); // Logs "var: 3" three times
}

// `let` - each iteration gets its OWN `i`
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(`let: ${i}`), 10); // Logs "let: 0", "let: 1", "let: 2"
}

Q97: What is a TypedArray? A TypedArray is an array-like object for reading and writing raw binary data. They are not regular arrays, but a family of objects (like Int8Array, Float32Array, etc.) that provide a typed view over an ArrayBuffer. They are highly efficient for working with binary data, such as in WebGL, file manipulation, or networking.

// Create an ArrayBuffer of 8 bytes
const buffer = new ArrayBuffer(8);

// Create a view on the buffer that treats the data as 32-bit signed integers
// 8 bytes / 4 bytes per integer = 2 integers
const int32View = new Int32Array(buffer);

int32View[0] = 42;
int32View[1] = 100;

console.log(int32View); // Int32Array(2) [42, 100]

// Create another view on the SAME buffer, treating it as 8-bit integers
const int8View = new Int8Array(buffer);
console.log(int8View); // Int8Array(8) [42, 0, 0, 0, 100, 0, 0, 0] (due to little-endian representation)

Q98: Explain Array.prototype.reduce The reduce() method executes a user-supplied “reducer” callback function on each element of the array, in order, passing in the return value from the calculation on the preceding element. The final result of running the reducer across all elements of the array is a single value.

// --- Example 1: Summing numbers ---
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((accumulator, currentValue) => {
    return accumulator + currentValue;
}, 0); // 0 is the initial value for the accumulator
console.log(sum); // 15

// --- Example 2: Grouping objects by a property ---
const people = [
    { name: 'Alice', role: 'dev' },
    { name: 'Bob', role: 'dev' },
    { name: 'Charlie', role: 'qa' }
];

const groupedByRole = people.reduce((acc, person) => {
    const key = person.role;
    if (!acc[key]) {
        acc[key] = [];
    }
    acc[key].push(person);
    return acc;
}, {}); // Initial value is an empty object
/*
groupedByRole is now:
{
  dev: [ { name: 'Alice', role: 'dev' }, { name: 'Bob', role: 'dev' } ],
  qa: [ { name: 'Charlie', role: 'qa' } ]
}
*/
console.log(groupedByRole);

Q99: What is dependency injection? Dependency Injection (DI) is a design pattern where a class receives its dependencies from an external source rather than creating them itself. This promotes loose coupling and makes code more modular, testable, and maintainable.

// --- Without DI ---
class BadUserService {
    private logger;
    constructor() {
        this.logger = new ConsoleLogger(); // Tight coupling, hard to test
    }
    createUser() {
        this.logger.log("User created");
    }
}

// --- With DI ---
interface ILogger {
    log(message: string): void;
}

class ConsoleLogger implements ILogger {
    log(message: string) { console.log(message); }
}

class FileLogger implements ILogger {
    log(message: string) { /* logic to write to a file */ }
}

// The class receives its dependency via the constructor
class GoodUserService {
    constructor(private logger: ILogger) {} // Dependency is "injected"

    createUser(name: string) {
        this.logger.log(`User "${name}" created.`);
    }
}

// The "injector" or "container" is responsible for creating dependencies
const consoleLogger = new ConsoleLogger();
const fileLogger = new FileLogger();

const service1 = new GoodUserService(consoleLogger); // Injecting ConsoleLogger
service1.createUser("Alice");

const service2 = new GoodUserService(fileLogger); // Injecting FileLogger
service2.createUser("Bob");

Q100: How do you handle errors in Promise.all? Promise.all is “fail-fast”—it rejects as soon as one of the input promises rejects. To handle this and still get results from the successful promises, you should use Promise.allSettled. If you must use Promise.all, you can map each promise to a new promise that always resolves.

const p1 = Promise.resolve('Success 1');
const p2 = Promise.reject('Failure!');
const p3 = Promise.resolve('Success 3');

// --- Method 1: Promise.allSettled (Preferred) ---
Promise.allSettled([p1, p2, p3])
  .then(results => {
    results.forEach(result => {
      if (result.status === 'fulfilled') {
        console.log(`Fulfilled: ${result.value}`);
      } else {
        console.error(`Rejected: ${result.reason}`);
      }
    });
  });

// --- Method 2: Manually handling errors for Promise.all ---
Promise.all([
    p1.catch(error => ({ status: 'rejected', reason: error })),
    p2.catch(error => ({ status: 'rejected', reason: error })),
    p3.catch(error => ({ status: 'rejected', reason: error })),
]).then(results => {
    console.log('Manual Handling Results:', results);
});

Q101: What are assertion functions in TypeScript? An assertion function is a function that checks for a condition and throws an error if the condition is not met. Its special asserts return type signature tells the TypeScript compiler to narrow the type of a variable for the rest of the scope, just as if an if statement or typeof check had been performed.

// The assertion function with the 'asserts' keyword
function assertIsString(value: unknown): asserts value is string {
    if (typeof value !== "string") {
        throw new Error("Not a string!");
    }
}

function processValue(input: string | number) {
    // Here, 'input' is 'string | number'
    assertIsString(input);
    
    // After the assertion, TypeScript knows 'input' MUST be a string.
    // So, this is now safe:
    console.log(input.toUpperCase());
}

processValue("hello"); // HELLO
// processValue(123); // Throws Error: "Not a string!"

Q102: Explain mapped types in TypeScript A mapped type is a generic type that creates a new object type by iterating over the keys of another type (keyof T) and transforming the properties. Common utility types like Partial<T>, Readonly<T>, and Pick<T, K> are implemented using mapped types.

// --- Basic Readonly implementation ---
type MyReadonly<T> = {
    readonly [P in keyof T]: T[P];
};

interface User {
    name: string;
    age: number;
}

const user: MyReadonly<User> = { name: "Alice", age: 30 };
// user.age = 31; // Error: Cannot assign to 'age' because it is a read-only property.

// --- Example: Creating a type with boolean flags for each property ---
type PropertyFlags<T> = {
    [P in keyof T]: boolean;
};

type UserFlags = PropertyFlags<User>;
// UserFlags is equivalent to: { name: boolean; age: boolean; }
const flags: UserFlags = { name: true, age: false };

Q103: How can you create an object with a null prototype? You can create an object with a null prototype using Object.create(null). Such an object does not inherit any properties or methods from Object.prototype, such as hasOwnProperty, toString, or constructor. Benefits:

  1. True Dictionary: It’s a “pure” key-value store. You don’t have to worry about accidentally overwriting a built-in method or checking for inherited properties with hasOwnProperty.
  2. Slight Performance: May offer a marginal performance benefit in some JS engines due to a shorter prototype chain to traverse.
const pureMap = Object.create(null);

pureMap.name = "John";
pureMap.toString = "A custom string"; // No conflict with Object.prototype.toString

console.log(pureMap.name); // "John"
console.log(pureMap.toString); // "A custom string"

// The following would throw errors because the methods don't exist:
// pureMap.hasOwnProperty('name'); // TypeError
// pureMap.constructor; // undefined

// In contrast, a regular object has inherited properties:
const regularObj = {};
console.log(regularObj.toString()); // "[object Object]"

Q104: What is the this problem in class methods in JS? JavaScript class methods are not automatically bound to the class instance. When a method is passed as a callback (e.g., to an event listener or setTimeout), its this context is lost. In strict mode (default for classes), this becomes undefined inside the callback, leading to a TypeError when you try to access instance properties like this.name.

Code Example Demonstrating the Problem and Solutions:

class Greeter {
    constructor(public message: string) {
        // Solution 1: Bind 'this' in the constructor (common in older React classes)
        // this.greet = this.greet.bind(this);
    }

    // This method has the 'this' problem
    greet() {
        console.log(this.message);
    }

    // Solution 2: Use an arrow function as a class field (modern approach)
    greetWithArrow = () => {
        console.log(this.message);
    }
}

const greeter = new Greeter("Hello, world!");

// --- The Problem ---
// `setTimeout`'s context is the global object (or `undefined` in strict mode)
// setTimeout(greeter.greet, 500); // Throws: TypeError: Cannot read properties of undefined (reading 'message')

// --- The Solutions ---
// 1. (If uncommented in constructor):
// setTimeout(greeter.greet, 500); // Works: "Hello, world!"

// 2. Use the arrow function version
setTimeout(greeter.greetWithArrow, 1000); // Works: "Hello, world!"

// 3. Bind at the call site
setTimeout(greeter.greet.bind(greeter), 1500); // Works: "Hello, world!"