All topics

JavaScript Fundamentals

Core JavaScript concepts and patterns

1. Implement Deep Flatten for Nested Arrays

intermediate

You need to implement a function that flattens a deeply nested array of any depth. Example: const nested = [1, [2, [3, [4]], 5], 6]; const result = flattenDeep(nested); console.log(result); // Should output: [1, 2, 3, 4, 5, 6] Implement the flattenDeep function that handles arrays nested at multiple levels.

javascript
function flattenDeep(arr) {
  return arr.reduce((acc, val) => {
    return Array.isArray(val) 
      ? acc.concat(flattenDeep(val))
      : acc.concat(val);
  }, []);
}

// Alternative modern approach
function flattenDeep2(arr) {
  return arr.flat(Infinity);
}

// Test cases
const nested = [1, [2, [3, [4]], 5], 6];
console.log(flattenDeep(nested));

const complex = [1, [2], [[3, [4, [5]]]], 6];
console.log(flattenDeep2(complex));

Answer: [1, 2, 3, 4, 5, 6] [1, 2, 3, 4, 5, 6]

SOLUTION EXPLANATION: The recursive approach: 1. Use reduce to iterate through each element 2. Check if current element is an array using Array.isArray() 3. If array, recursively call flattenDeep and concatenate results 4. If not array, directly concatenate value to accumulator 5. Base case: non-array values are returned as-is TIME COMPLEXITY: O(n) where n is total number of elements SPACE COMPLEXITY: O(d) where d is maximum depth (recursion stack) MODERN ALTERNATIVE: The flat(Infinity) method is ES2019 built-in that flattens to any depth. However, interviewers want to see manual implementation to test recursion understanding. COMMON MISTAKES: - Forgetting to handle empty arrays - Not using recursion for deep nesting - Mutating original array ASKED AT: Google, Amazon, Microsoft, Meta

arraysrecursionalgorithm

2. Group Array of Objects by Property

intermediate

Given an array of transactions, group them by userId. Implement a reusable groupBy function. const transactions = [ { userId: 1, amount: 50, type: "credit" }, { userId: 2, amount: 100, type: "debit" }, { userId: 1, amount: 75, type: "debit" }, { userId: 3, amount: 200, type: "credit" }, { userId: 2, amount: 50, type: "credit" } ]; const grouped = groupBy(transactions, "userId"); // Expected: { 1: [{...}, {...}], 2: [{...}, {...}], 3: [{...}] }

javascript
function groupBy(array, key) {
  return array.reduce((result, obj) => {
    const keyValue = obj[key];
    if (!result[keyValue]) {
      result[keyValue] = [];
    }
    result[keyValue].push(obj);
    return result;
  }, {});
}

const transactions = [
  { userId: 1, amount: 50, type: "credit" },
  { userId: 2, amount: 100, type: "debit" },
  { userId: 1, amount: 75, type: "debit" },
  { userId: 3, amount: 200, type: "credit" },
  { userId: 2, amount: 50, type: "credit" }
];

const result = groupBy(transactions, "userId");
console.log(JSON.stringify(result, null, 2));

Answer: { "1": [ {"userId": 1, "amount": 50, "type": "credit"}, {"userId": 1, "amount": 75, "type": "debit"} ], "2": [ {"userId": 2, "amount": 100, "type": "debit"}, {"userId": 2, "amount": 50, "type": "credit"} ], "3": [ {"userId": 3, "amount": 200, "type": "credit"} ] }

SOLUTION EXPLANATION: The groupBy function transforms array into grouped object: 1. Initialize empty object as accumulator 2. Extract value of grouping key from each object 3. Create empty array for key if it does not exist 4. Push current object to array for that key 5. Return updated accumulator KEY CONCEPTS: - Dynamic property access using obj[key] - Reduce pattern for array-to-object transformation - Object property initialization checking REAL-WORLD USES: - Analytics data grouping by date or category - Organizing API responses by status - Aggregating e-commerce orders by customer - Dashboard data preparation TIME COMPLEXITY: O(n) single pass SPACE COMPLEXITY: O(n) stores all objects ASKED AT: Stripe, Shopify, Square

arraysreduceobjectsdata-transformation

3. Fix Lost Context in Event Handlers

advanced

This component has a bug where clicking shows "undefined" instead of the user name. Fix it. class UserProfile { constructor(name) { this.name = name; this.clicks = 0; } handleClick() { this.clicks++; console.log(this.name + " clicked " + this.clicks + " times"); } render() { const button = document.createElement("button"); button.onclick = this.handleClick; // BUG HERE return button; } } const profile = new UserProfile("Alice"); profile.render().click(); // Shows: undefined clicked NaN times

javascript
class UserProfile {
  constructor(name) {
    this.name = name;
    this.clicks = 0;
  }
  
  // SOLUTION: Arrow function preserves context
  handleClick = () => {
    this.clicks++;
    console.log(this.name + " clicked " + this.clicks + " times");
  }
  
  render() {
    const button = document.createElement("button");
    button.onclick = this.handleClick;
    return button;
  }
}

// Alternative: Bind in constructor
// constructor(name) {
//   this.name = name;
//   this.clicks = 0;
//   this.handleClick = this.handleClick.bind(this);
// }

const profile = new UserProfile("Alice");
profile.render().click();

const profile2 = new UserProfile("Bob");
profile2.render().click();
profile2.render().click();

Answer: Alice clicked 1 times Bob clicked 1 times Bob clicked 2 times

THE PROBLEM: When assigning a method to event handler, it loses context. The this keyword no longer refers to class instance. SOLUTIONS: 1. ARROW FUNCTION (Recommended): - Arrow functions inherit this from enclosing scope - Automatically binds at definition time - Most common in React and modern classes 2. BIND IN CONSTRUCTOR: - Explicitly creates bound version - Better for performance if called frequently - More verbose but explicit 3. BIND INLINE (Not recommended): - button.onclick = this.handleClick.bind(this) - Creates new function every render - Poor performance WHY THIS MATTERS: - Core JS concept tested in all FAANG interviews - Critical for React class components - Common bug in production code - Tests execution context understanding ASKED AT: Meta, Google, Amazon, Netflix, Airbnb

this-keywordcontextclassesdebugging

4. Implement Debounce for Search Optimization

intermediate

You are building a search feature that calls an API on every keystroke. This causes performance issues. Implement a debounce function that delays execution until user stops typing for 500 milliseconds. const search = (query) => { console.log("API call for:", query); }; const debouncedSearch = debounce(search, 500); // User types: j-a-v-a-s-c-r-i-p-t (10 keystrokes) // Should only make 1 API call with "javascript"

javascript
function debounce(func, delay) {
  let timeoutId;
  
  return function(...args) {
    clearTimeout(timeoutId);
    
    timeoutId = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  };
}

// Usage
const search = (query) => {
  console.log("API call for:", query);
};

const debouncedSearch = debounce(search, 500);

// Simulate typing
debouncedSearch("j");
debouncedSearch("ja");
debouncedSearch("jav");
debouncedSearch("java");
debouncedSearch("javasc");
debouncedSearch("javascr");
debouncedSearch("javascri");
debouncedSearch("javascrip");
debouncedSearch("javascript");

Answer: API call for: javascript

HOW DEBOUNCING WORKS: 1. Store timeout ID in closure 2. On each call, clear previous timeout 3. Set new timeout with specified delay 4. Only executes if no new calls within delay period 5. Uses apply to preserve this context and pass arguments KEY CONCEPTS: - Closures to maintain timeoutId state - setTimeout for delayed execution - clearTimeout to cancel previous calls - apply to preserve context REAL-WORLD APPLICATIONS: - Search input fields (Google, Amazon) - Window resize handlers - Scroll event listeners - Auto-save functionality - Form validation PERFORMANCE IMPACT: - Without debounce: 10 keystrokes = 10 API calls - With debounce: 10 keystrokes = 1 API call - Reduces bandwidth by 90 percent - Improves server load and UX DEBOUNCE vs THROTTLE: - Debounce: Wait until quiet period ends - Throttle: Execute at most once per interval ASKED AT: Google, Airbnb, Uber, Netflix (very common)

debounceperformanceclosuresoptimization

5. Implement API Retry Logic with Exponential Backoff

advanced

Your API calls sometimes fail due to network issues. Implement a retry function that attempts an API call up to 3 times with exponential backoff (1 sec, 2 sec, 4 sec delays). const fetchData = () => { return fetch("/api/data").then(res => res.json()); }; const result = await retryWithBackoff(fetchData, 3); If all retries fail, throw the last error.

javascript
async function retryWithBackoff(fn, maxRetries, baseDelay = 1000) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (attempt === maxRetries) {
        throw error;
      }
      
      const delay = baseDelay * Math.pow(2, attempt);
      console.log(`Attempt ${attempt + 1} failed. Retrying in ${delay} milliseconds...`);
      
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

// Simulate flaky API
let callCount = 0;
const flakyAPI = async () => {
  callCount++;
  console.log(`API call #${callCount}`);
  if (callCount < 3) {
    throw new Error("Network error");
  }
  return { data: "Success!" };
};

// Test
retryWithBackoff(flakyAPI, 3)
  .then(result => console.log("Final result:", result))
  .catch(err => console.log("All retries failed:", err.message));

Answer: API call #1 Attempt 1 failed. Retrying in 1000 milliseconds... API call #2 Attempt 2 failed. Retrying in 2000 milliseconds... API call #3 Final result: { data: "Success!" }

SOLUTION BREAKDOWN: 1. Loop through retry attempts (0 to maxRetries) 2. Try executing the function 3. If succeeds, return result immediately 4. If fails and attempts remain, calculate exponential delay 5. Wait for delay period before next attempt 6. If all attempts exhausted, throw last error EXPONENTIAL BACKOFF: - Attempt 1: 1000 milliseconds (baseDelay * 2^0) - Attempt 2: 2000 milliseconds (baseDelay * 2^1) - Attempt 3: 4000 milliseconds (baseDelay * 2^2) WHY EXPONENTIAL: - Reduces server load during outages - Gives system time to recover - Industry standard (AWS, Google Cloud) - Prevents thundering herd problem KEY CONCEPTS: - Async/await error handling - Promise-based delays - Exponential algorithms - Loop-based retry logic REAL-WORLD USAGE: - Microservices communication - Third-party API integration - Database connection retries - Message queue consumers ASKED AT: Amazon, Microsoft, Netflix, Stripe

promisesasync-awaiterror-handlingretry-logic

6. Find First Non-Repeating Character in String

beginner

Given a string, find the first character that appears only once. Return null if none exists. Examples: firstUnique("leetcode") // "l" firstUnique("loveleetcode") // "v" firstUnique("aabb") // null Optimize for time complexity.

javascript
function firstUnique(str) {
  const charCount = {};
  
  // First pass: count occurrences
  for (const char of str) {
    charCount[char] = (charCount[char] || 0) + 1;
  }
  
  // Second pass: find first with count 1
  for (const char of str) {
    if (charCount[char] === 1) {
      return char;
    }
  }
  
  return null;
}

// Alternative using Map
function firstUnique2(str) {
  const map = new Map();
  
  for (const char of str) {
    map.set(char, (map.get(char) || 0) + 1);
  }
  
  for (const char of str) {
    if (map.get(char) === 1) return char;
  }
  
  return null;
}

// Test cases
console.log(firstUnique("leetcode"));
console.log(firstUnique("loveleetcode"));
console.log(firstUnique("aabb"));
console.log(firstUnique(""));

Answer: l v null null

ALGORITHM EXPLANATION: TWO-PASS APPROACH: 1. First pass: Count frequency of each character in hash map 2. Second pass: Find first character with frequency 1 3. Return null if no unique character found WHY TWO PASSES: - Cannot determine uniqueness in single pass - Need complete frequency data before deciding - Maintains order of first occurrence COMPLEXITY ANALYSIS: - Time: O(n) where n is string length (two linear passes) - Space: O(k) where k is unique characters (max 26 for lowercase) OBJECT vs MAP: - Objects are simpler, work for most cases - Maps maintain insertion order (not needed here) - Maps better for non-string keys - Maps have size property EDGE CASES TO CONSIDER: - Empty string returns null - All characters repeat returns null - Single character returns that character - Case sensitivity (treat A and a as different) OPTIMIZATION: Cannot do better than O(n) time since we must examine every character at least once. ASKED AT: Amazon, Google, Meta (common string problem)

stringshash-mapalgorithmtwo-pointer

7. Deep Clone Object with Circular References

advanced

Implement deepClone that handles nested objects, arrays, and circular references. const obj = { name: "John", address: { city: "NYC" } }; obj.self = obj; // Circular reference const cloned = deepClone(obj); console.log(cloned.name); // "John" console.log(cloned.self === cloned); // true console.log(cloned === obj); // false JSON.parse(JSON.stringify(obj)) would fail here with "Converting circular structure to JSON".

javascript
function deepClone(obj, hash = new WeakMap()) {
  // Handle primitives and null
  if (obj === null || typeof obj !== "object") {
    return obj;
  }
  
  // Handle circular references
  if (hash.has(obj)) {
    return hash.get(obj);
  }
  
  // Handle Date
  if (obj instanceof Date) {
    return new Date(obj);
  }
  
  // Handle Array
  if (Array.isArray(obj)) {
    const arrCopy = [];
    hash.set(obj, arrCopy);
    obj.forEach((item, index) => {
      arrCopy[index] = deepClone(item, hash);
    });
    return arrCopy;
  }
  
  // Handle Object
  const objCopy = {};
  hash.set(obj, objCopy);
  
  Object.keys(obj).forEach(key => {
    objCopy[key] = deepClone(obj[key], hash);
  });
  
  return objCopy;
}

// Test with circular reference
const obj = { 
  name: "John", 
  address: { city: "NYC" },
  hobbies: ["reading", "coding"]
};
obj.self = obj;

const cloned = deepClone(obj);
console.log(cloned.name);
console.log(cloned.address.city);
console.log(cloned.self === cloned);
console.log(cloned === obj);

Answer: John NYC true false

SOLUTION BREAKDOWN: 1. HANDLE PRIMITIVES: Return immediately if not object 2. CHECK CIRCULAR: Use WeakMap to track visited objects 3. HANDLE SPECIAL TYPES: Date, Array need special handling 4. RECURSIVE CLONE: Clone nested structures recursively 5. STORE REFERENCE: Add to WeakMap before recursing WHY WEAKMAP: - Allows garbage collection of cloned objects - Keys must be objects (perfect for our use case) - Does not prevent original objects from being GC'd - Better memory management than regular Map WHY JSON.parse(JSON.stringify()) FAILS: - Throws on circular references - Loses functions - Loses undefined values - Cannot handle Date, RegExp, Map, Set properly - Converts Infinity to null MODERN ALTERNATIVE: structuredClone(obj) handles most cases but: - Cannot clone functions - No IE support - May not handle all custom objects TYPES TO HANDLE: - Primitives: string, number, boolean, null, undefined - Objects: plain objects, nested objects - Arrays: including nested arrays - Dates: create new Date instance - Functions: typically reference (not clone) - Circular references: use WeakMap ASKED AT: Meta, Google, Amazon (senior positions)

objectsrecursioncloningcircular-references

8. Implement Promise.all Without Using Built-In

advanced

Implement your own version of Promise.all that: 1. Takes array of promises 2. Returns promise that resolves when all resolve 3. Rejects immediately if any promise rejects 4. Resolves with array of results in same order const p1 = Promise.resolve(3); const p2 = new Promise(resolve => setTimeout(() => resolve(2), 100)); const p3 = Promise.resolve(1); myPromiseAll([p1, p2, p3]).then(console.log); // [3, 2, 1]

javascript
function myPromiseAll(promises) {
  return new Promise((resolve, reject) => {
    const results = [];
    let completed = 0;
    
    // Handle empty array
    if (promises.length === 0) {
      resolve(results);
      return;
    }
    
    promises.forEach((promise, index) => {
      // Ensure it is a promise
      Promise.resolve(promise)
        .then(value => {
          results[index] = value;
          completed++;
          
          if (completed === promises.length) {
            resolve(results);
          }
        })
        .catch(error => {
          reject(error);
        });
    });
  });
}

// Test success case
const p1 = Promise.resolve(3);
const p2 = new Promise(resolve => setTimeout(() => resolve(2), 100));
const p3 = Promise.resolve(1);

myPromiseAll([p1, p2, p3])
  .then(result => console.log("Success:", result));

// Test failure case
const p4 = Promise.resolve(1);
const p5 = Promise.reject("Error!");
const p6 = Promise.resolve(3);

myPromiseAll([p4, p5, p6])
  .then(result => console.log("Will not see this"))
  .catch(error => console.log("Failed:", error));

Answer: Success: [3, 2, 1] Failed: Error!

IMPLEMENTATION DETAILS: 1. Return new Promise wrapper 2. Track results array and completion counter 3. Handle empty array edge case 4. Iterate through promises with forEach 5. Wrap each in Promise.resolve (handles non-promises) 6. Store results at correct index (maintains order) 7. Increment counter on each success 8. Resolve when all complete 9. Reject immediately on first failure KEY INSIGHTS: WHY results[index]: - Promises may resolve out of order - Must maintain input array order - Cannot use results.push() WHY Promise.resolve(promise): - Input might contain non-promise values - Ensures everything is treated as promise - Example: [1, 2, Promise.resolve(3)] WHY completed counter: - Cannot rely on results.length - Sparse array: results[5] = val creates length 6 - Need exact count of completed promises EDGE CASES: - Empty array: resolve immediately - Mix of promises and values - Promise that rejects immediately - All promises resolve at same time DIFFERENCE FROM PROMISE.ALLSETTLED: - Promise.all: rejects on first failure - Promise.allSettled: waits for all, never rejects ASKED AT: Meta, Google, Amazon (promise understanding)

promisesasyncimplementationpromise-all

9. Implement Least Recently Used (LRU) Cache

advanced

Design and implement an LRU Cache with O(1) get and put operations. const cache = new LRUCache(2); // capacity = 2 cache.put(1, "A"); cache.put(2, "B"); console.log(cache.get(1)); // "A" cache.put(3, "C"); // evicts key 2 console.log(cache.get(2)); // null cache.put(4, "D"); // evicts key 1 console.log(cache.get(1)); // null console.log(cache.get(3)); // "C" console.log(cache.get(4)); // "D"

javascript
class LRUCache {
  constructor(capacity) {
    this.capacity = capacity;
    this.cache = new Map();
  }
  
  get(key) {
    if (!this.cache.has(key)) {
      return null;
    }
    
    // Move to end (most recently used)
    const value = this.cache.get(key);
    this.cache.delete(key);
    this.cache.set(key, value);
    return value;
  }
  
  put(key, value) {
    // Remove if exists (to update position)
    if (this.cache.has(key)) {
      this.cache.delete(key);
    }
    
    // Add to end
    this.cache.set(key, value);
    
    // Evict least recently used if over capacity
    if (this.cache.size > this.capacity) {
      const firstKey = this.cache.keys().next().value;
      this.cache.delete(firstKey);
    }
  }
}

// Test
const cache = new LRUCache(2);
cache.put(1, "A");
cache.put(2, "B");
console.log(cache.get(1)); // "A" - moves 1 to end
cache.put(3, "C"); // evicts 2
console.log(cache.get(2)); // null
cache.put(4, "D"); // evicts 1
console.log(cache.get(1)); // null
console.log(cache.get(3)); // "C"
console.log(cache.get(4)); // "D"

Answer: A null null C D

SOLUTION EXPLANATION: MAP MAINTAINS INSERTION ORDER: - JavaScript Map remembers insertion order - First item is least recently used - Last item is most recently used - Perfect for LRU implementation GET OPERATION: 1. Check if key exists, return null if not 2. Get value before deletion 3. Delete key from current position 4. Re-insert at end (marks as recently used) 5. Return value PUT OPERATION: 1. If key exists, delete it (will re-add at end) 2. Insert key-value pair at end 3. If over capacity, evict first key 4. First key is least recently used WHY O(1): - Map.get(): O(1) - Map.set(): O(1) - Map.delete(): O(1) - Map.keys().next().value: O(1) for first item ALTERNATIVE APPROACH: Use doubly linked list + hash map: - More complex to implement - Same O(1) performance - Better for interviews to show data structure knowledge REAL-WORLD USAGE: - Browser cache - CDN cache - Database query cache - API response cache - React useMemo/useCallback EVICTION POLICIES: - LRU: Least Recently Used (this implementation) - LFU: Least Frequently Used - FIFO: First In First Out - TTL: Time To Live ASKED AT: Amazon, Microsoft, Google (very common)

cachelrudata-structuresoptimization

10. Implement Throttle for Scroll Performance

intermediate

You have a scroll event handler that fires hundreds of times per second, causing performance issues. Implement a throttle function that ensures the handler executes at most once every 1 second (1000 milliseconds), regardless of how many times it is called. const handleScroll = () => { console.log("Scroll position:", window.scrollY); }; const throttledScroll = throttle(handleScroll, 1000); window.addEventListener("scroll", throttledScroll); During fast scrolling, should only log once per second.

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

// Alternative with leading and trailing options
function throttleAdvanced(func, limit, options = {}) {
  let timeout;
  let previous = 0;
  
  return function(...args) {
    const now = Date.now();
    const remaining = limit - (now - previous);
    
    if (remaining <= 0) {
      if (timeout) {
        clearTimeout(timeout);
        timeout = null;
      }
      previous = now;
      func.apply(this, args);
    }
  };
}

// Test
let callCount = 0;
const logCall = () => {
  callCount++;
  console.log("Call", callCount);
};

const throttled = throttle(logCall, 1000);

// Simulate rapid calls
setInterval(throttled, 100); // Called every 100 milliseconds
// Only logs once per second

Answer: Call 1 Call 2 Call 3 Call 4 ...

THROTTLE vs DEBOUNCE: THROTTLE: - Executes at most once per interval - First call executes immediately - Subsequent calls ignored until interval passes - Use: scroll, resize, mousemove events DEBOUNCE: - Waits for quiet period before executing - Delays execution until calls stop - Resets timer on each call - Use: search input, auto-save, form validation HOW THROTTLE WORKS: 1. Track throttle state with inThrottle flag 2. First call executes immediately 3. Set inThrottle to true 4. Start timeout for limit duration 5. Ignore subsequent calls while inThrottle is true 6. After timeout, reset inThrottle to false 7. Next call can execute KEY CONCEPTS: - Closure to maintain state - Boolean flag for throttle status - setTimeout for delay - apply to preserve context PERFORMANCE COMPARISON: - Without throttle: 100 events per second = 100 executions - With throttle (1 second): 100 events per second = 1 execution - 99 percent reduction in function calls ADVANCED OPTIONS: - Leading: Execute on first call (default true) - Trailing: Execute after interval ends (optional) - Lodash throttle has both options REAL-WORLD EXAMPLES: - Infinite scroll loading - Window resize handlers - Mouse move tracking - Drag and drop - Game loop updates ASKED AT: Google, Airbnb, Uber, Twitter

throttleperformanceeventsoptimization

11. Implement Curry Function Transformation

intermediate

Currying is the technique of converting a function that takes multiple arguments into a sequence of functions that each take a single argument. Implement a curry function. Example: 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 console.log(curriedSum(1)(2, 3)); // 6

javascript
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } else {
      return function(...nextArgs) {
        return curried.apply(this, args.concat(nextArgs));
      };
    }
  };
}

// Test cases
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
console.log(curriedSum(1)(2, 3)); // 6
console.log(curriedSum(1, 2, 3)); // 6

// Practical use case
const multiply = (a, b) => a * b;
const double = curry(multiply)(2);
console.log(double(5)); // 10
console.log(double(10)); // 20

Answer: 6 6 6 6 10 20

CURRYING CONCEPT: Currying transforms f(a, b, c) into f(a)(b)(c). IMPLEMENTATION DETAILS: 1. Return a curried function 2. Track number of arguments received 3. If arguments count >= original function length, execute original function 4. Otherwise, return new function that accumulates more arguments 5. Use recursion to handle any number of arguments KEY POINTS: - fn.length returns number of parameters function expects - apply preserves this context - Recursive accumulation of arguments - Works with any number of arguments REAL-WORLD USES: - Function composition - Creating specialized functions (double, triple) - Event handler factories - Configuration builders - Redux middleware patterns FUNCTIONAL PROGRAMMING: - Pure functions with no side effects - Function composition - Partial application (different from currying) - Higher-order functions PERFORMANCE CONSIDERATIONS: - Creates multiple function objects - Modern engines optimize well - Use for readability, not performance ASKED AT: Meta, Google, Netflix (frontend positions)

curryingfunctional-programmingclosuresfunctions

12. Implement Memoization for Expensive Functions

intermediate

Memoization is an optimization technique that caches results of expensive function calls. Implement a memoize function that works for any function. Example: const expensiveCalc = (n) => { console.log("Computing..."); return n * 2; }; const memoizedCalc = memoize(expensiveCalc); console.log(memoizedCalc(5)); // Computing... then 10 console.log(memoizedCalc(5)); // 10 (cached, no "Computing...") console.log(memoizedCalc(6)); // Computing... then 12

javascript
function memoize(fn) {
  const cache = new Map();
  
  return function(...args) {
    const key = JSON.stringify(args);
    
    if (cache.has(key)) {
      return cache.get(key);
    }
    
    const result = fn.apply(this, args);
    cache.set(key, result);
    return result;
  };
}

// Test with expensive calculation
const expensiveCalc = (n) => {
  console.log("Computing...");
  // Simulate expensive operation
  return n * 2;
};

const memoizedCalc = memoize(expensiveCalc);
console.log(memoizedCalc(5)); // Computing... then 10
console.log(memoizedCalc(5)); // 10 (cached)
console.log(memoizedCalc(6)); // Computing... then 12
console.log(memoizedCalc(6)); // 12 (cached)

// Test with multiple arguments
const add = (a, b) => a + b;
const memoizedAdd = memoize(add);
console.log(memoizedAdd(2, 3)); // 5
console.log(memoizedAdd(2, 3)); // 5 (cached)

Answer: Computing... 10 10 Computing... 12 12 5 5

MEMOIZATION EXPLAINED: Memoization stores function results to avoid recomputation. IMPLEMENTATION DETAILS: 1. Create closure with cache (Map for O(1) lookups) 2. Serialize arguments to create cache key 3. Check cache before computation 4. Store result if not cached 5. Return cached result if available WHY JSON.stringify(): - Creates string key from any arguments - Works with primitive and object arguments - Watch out for circular references - Order matters: fn(1, 2) != fn(2, 1) ADVANCED CONSIDERATIONS: - Cache size limits (LRU eviction) - Custom key generators - Time-based expiration (TTL) - Memory management - WeakMap for object keys REAL-WORLD APPLICATIONS: - Fibonacci sequence calculation - API response caching - Expensive mathematical operations - React useMemo hook - GraphQL resolver caching PERFORMANCE BENEFITS: - O(1) cache lookup vs O(n) recomputation - Dramatic speedup for recursive functions - Trade memory for computation time ASKED AT: Google, Amazon, Microsoft (performance optimization)

memoizationoptimizationcacheperformance

13. Implement Promise.race from Scratch

intermediate

Implement your own version of Promise.race that returns a promise that resolves or rejects as soon as one of the promises in the array resolves or rejects. Example: const p1 = new Promise(resolve => setTimeout(() => resolve("p1"), 100)); const p2 = new Promise(resolve => setTimeout(() => resolve("p2"), 50)); const p3 = new Promise((_, reject) => setTimeout(() => reject("p3 error"), 10)); myPromiseRace([p1, p2, p3]) .then(console.log) // Not called .catch(console.log); // "p3 error"

javascript
function myPromiseRace(promises) {
  return new Promise((resolve, reject) => {
    promises.forEach(promise => {
      Promise.resolve(promise)
        .then(resolve)
        .catch(reject);
    });
  });
}

// Test cases
const p1 = new Promise(resolve => 
  setTimeout(() => resolve("First"), 100)
);
const p2 = new Promise(resolve => 
  setTimeout(() => resolve("Second"), 50)
);
const p3 = new Promise((_, reject) => 
  setTimeout(() => reject("Error!"), 10)
);

// Should reject with "Error!" (fastest)
myPromiseRace([p1, p2, p3])
  .then(result => console.log("Success:", result))
  .catch(error => console.log("Failed:", error));

// Test with regular values
const p4 = Promise.resolve("Immediate");
const p5 = new Promise(resolve => 
  setTimeout(() => resolve("Delayed"), 100)
);

// Should resolve with "Immediate"
myPromiseRace([p4, p5])
  .then(result => console.log("Race result:", result));

Answer: Failed: Error! Race result: Immediate

PROMISE.RACE CONCEPT: Returns promise that settles when first input promise settles. IMPLEMENTATION DETAILS: 1. Create new promise wrapper 2. Iterate through all promises 3. Attach then and catch handlers to each 4. First promise to settle determines outcome 5. Other promises continue but are ignored KEY DIFFERENCES: - Promise.race: first to settle (resolve OR reject) - Promise.any: first to resolve (ignores rejections) - Promise.all: all must resolve - Promise.allSettled: wait for all IMPORTANT EDGE CASES: - Empty array: pending forever - Non-promise values: treated as resolved promises - Already settled promises: immediate result - Multiple promises settling simultaneously REAL-WORLD USE CASES: - Timeout implementation - Race multiple API endpoints - Load balancer health checks - First successful operation - Fallback strategies TIMEOUT PATTERN: const timeout = (ms) => new Promise((_, reject) => setTimeout(() => reject("Timeout"), ms) ); // Race API call with timeout Promise.race([fetch("/api"), timeout(5000)]); ASKED AT: Amazon, Google, Microsoft (async patterns)

promisesasyncracetimeout

14. Implement Array Map Method from Scratch

beginner

Implement your own version of Array.prototype.map that works exactly like the built-in method. It should create a new array with the results of calling a provided function on every element. Example: const numbers = [1, 2, 3]; const doubled = numbers.myMap(n => n * 2); console.log(doubled); // [2, 4, 6]

javascript
Array.prototype.myMap = function(callback, thisArg) {
  const result = [];
  
  for (let i = 0; i < this.length; i++) {
    if (i in this) {
      result[i] = callback.call(thisArg, this[i], i, this);
    }
  }
  
  return result;
};

// Test cases
const numbers = [1, 2, 3, 4, 5];

// Basic usage
const doubled = numbers.myMap(n => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]

// With index
const withIndex = numbers.myMap((n, i) => n + i);
console.log(withIndex); // [1, 3, 5, 7, 9]

// With thisArg
const multiplier = {
  factor: 3,
  multiply(n) {
    return n * this.factor;
  }
};
const tripled = numbers.myMap(multiplier.multiply, multiplier);
console.log(tripled); // [3, 6, 9, 12, 15]

// Sparse arrays
const sparse = [1, , 3]; // index 1 is empty
const sparseResult = sparse.myMap(n => n * 2);
console.log(sparseResult); // [2, empty, 6]

Answer: [2, 4, 6, 8, 10] [1, 3, 5, 7, 9] [3, 6, 9, 12, 15] [2, empty, 6]

MAP METHOD DETAILS: Creates new array by transforming each element. IMPLEMENTATION KEY POINTS: 1. Accept callback function and optional thisArg 2. Iterate through array using for loop 3. Check i in this to handle sparse arrays 4. Call callback with: element, index, original array 5. Use call to set this context 6. Return new array SPARSE ARRAY HANDLING: - for loop with i in this check - Preserves empty slots - Same behavior as native map - Important interview test case CALLBACK PARAMETERS: 1. Current element 2. Current index 3. Original array THIS CONTEXT: - Default: undefined (or global in non-strict) - Can be set with thisArg parameter - Important for method extraction DIFFERENCES FROM FOREACH: - Map returns new array - ForEach returns undefined - Both iterate through elements PERFORMANCE CONSIDERATIONS: - O(n) time complexity - Creates new array (memory usage) - Native map is faster but same complexity ASKED AT: Meta, Google, Amazon (core JavaScript)

arraysmappolyfilliteration

15. Check if String is Palindrome (Multiple Approaches)

beginner

Write a function to check if a string is a palindrome (reads same forwards and backwards). Ignore case and non-alphanumeric characters. Examples: isPalindrome("racecar") // true isPalindrome("A man, a plan, a canal: Panama") // true isPalindrome("hello") // false isPalindrome("") // true (empty string) isPalindrome("a") // true

javascript
function isPalindrome(str) {
  // Clean string: lowercase and remove non-alphanumeric
  const clean = str.toLowerCase().replace(/[^a-z0-9]/g, "");
  
  // Two-pointer approach
  let left = 0;
  let right = clean.length - 1;
  
  while (left < right) {
    if (clean[left] !== clean[right]) {
      return false;
    }
    left++;
    right--;
  }
  
  return true;
}

// Alternative: Compare with reverse
function isPalindrome2(str) {
  const clean = str.toLowerCase().replace(/[^a-z0-9]/g, "");
  return clean === clean.split("").reverse().join("");
}

// Test cases
console.log(isPalindrome("racecar")); // true
console.log(isPalindrome("A man, a plan, a canal: Panama")); // true
console.log(isPalindrome("hello")); // false
console.log(isPalindrome("")); // true
console.log(isPalindrome("a")); // true
console.log(isPalindrome("race a car")); // false

// Performance comparison
console.time("Two-pointer");
for (let i = 0; i < 1000000; i++) isPalindrome("A man, a plan, a canal: Panama");
console.timeEnd("Two-pointer");

console.time("Reverse");
for (let i = 0; i < 1000000; i++) isPalindrome2("A man, a plan, a canal: Panama");
console.timeEnd("Reverse");

Answer: true true false true true false Two-pointer: ~50ms Reverse: ~120ms

PALINDROME ALGORITHMS: TWO-POINTER APPROACH (OPTIMAL): 1. Clean string: lowercase and remove non-alphanumeric 2. Initialize pointers at start and end 3. Compare characters moving towards center 4. Return false if mismatch found 5. Return true if all match REVERSE APPROACH (SIMPLER): 1. Clean string 2. Reverse string and compare 3. More readable but less efficient TIME COMPLEXITY: - Two-pointer: O(n) time, O(1) extra space - Reverse: O(n) time, O(n) extra space (new strings) REGEX EXPLANATION: /[^a-z0-9]/g - ^ means "not" - a-z matches lowercase letters - 0-9 matches digits - g flag for global match - Removes spaces, punctuation, symbols EDGE CASES: - Empty string: palindrome by definition - Single character: always palindrome - Case insensitive: convert to lowercase - Unicode characters: more complex handling - Whitespace and punctuation: ignore REAL-WORLD APPLICATIONS: - DNA sequence analysis - Data validation - Compression algorithms - Game development (word games) OPTIMIZATION: - Early exit on mismatch - Memory efficient (two-pointer) - Character code comparison possible ASKED AT: Amazon, Google, Meta (string algorithms)

stringspalindromealgorithmtwo-pointer

16. Implement Function.prototype.bind from Scratch

advanced

Implement your own version of Function.prototype.bind that works like the built-in method. It should return a new function with this context bound. Example: const person = { name: "John", greet(greeting, punctuation) { return `${greeting}, ${this.name}${punctuation}`; } }; const boundGreet = person.greet.myBind(person, "Hello"); console.log(boundGreet("!")); // "Hello, John!"

javascript
Function.prototype.myBind = function(context, ...bindArgs) {
  const originalFunc = this;
  
  return function(...callArgs) {
    return originalFunc.apply(context, [...bindArgs, ...callArgs]);
  };
};

// Test cases
const person = {
  name: "John",
  greet(greeting, punctuation) {
    return `${greeting}, ${this.name}${punctuation}`;
  }
};

// Basic binding
const boundGreet = person.greet.myBind(person, "Hello");
console.log(boundGreet("!")); // "Hello, John!"

// Partial application
const add = (a, b, c) => a + b + c;
const addFive = add.myBind(null, 5);
console.log(addFive(10, 15)); // 30 (5 + 10 + 15)

// Constructor binding (new keyword)
function Person(name) {
  this.name = name;
}

Person.prototype.sayName = function() {
  return `I'm ${this.name}`;
};

const BoundPerson = Person.myBind(null, "Alice");
const alice = new BoundPerson();
console.log(alice.sayName()); // "I'm Alice"
console.log(alice instanceof Person); // true

Answer: Hello, John! 30 I'm Alice true

BIND METHOD EXPLAINED: Creates new function with bound this and optional partial arguments. IMPLEMENTATION DETAILS: 1. Capture original function (this) 2. Return new function 3. Combine bound arguments and call arguments 4. Use apply to call with correct context KEY FEATURES: - Partial application (pre-specified arguments) - Context binding (this value) - Preserves function length property - Works with constructors (new keyword) ARGUMENT HANDLING: - bindArgs: arguments passed during binding - callArgs: arguments passed during call - Combined: [...bindArgs, ...callArgs] CONSTRUCTOR HANDLING: - When called with new, ignore bound context - instanceof should work correctly - Complex but rarely tested in interviews REAL-WORLD USES: - Event handler binding in React - Partial function application - Currying implementation - Method extraction from objects - Callback context preservation POLYFILL CONSIDERATIONS: - ES5 compatibility - Function length preservation - No new.target in ES5 - Edge cases with undefined/null ASKED AT: Meta, Google, Amazon (core JavaScript)

bindthis-keywordcontextpolyfill

17. Implement Observable/Pub-Sub Pattern

advanced

Implement an Observable class that follows the Observer pattern. It should allow subscribers to listen for events and receive notifications. Example: const observable = new Observable(); const unsubscribe = observable.subscribe(data => { console.log("Received:", data); }); observable.notify("Hello"); // Logs: "Received: Hello" unsubscribe(); observable.notify("World"); // No output

javascript
class Observable {
  constructor() {
    this.subscribers = new Set();
  }
  
  subscribe(callback) {
    this.subscribers.add(callback);
    
    return () => {
      this.subscribers.delete(callback);
    };
  }
  
  notify(data) {
    this.subscribers.forEach(callback => {
      try {
        callback(data);
      } catch (error) {
        console.error("Error in subscriber:", error);
      }
    });
  }
  
  clear() {
    this.subscribers.clear();
  }
}

// Test cases
const observable = new Observable();

// Subscribe
const logs = [];
const unsubscribe1 = observable.subscribe(data => {
  logs.push(`Sub1: ${data}`);
});

const unsubscribe2 = observable.subscribe(data => {
  logs.push(`Sub2: ${data}`);
});

// Notify
observable.notify("Hello");
console.log(logs); // ["Sub1: Hello", "Sub2: Hello"]

// Unsubscribe one
unsubscribe1();
observable.notify("World");
console.log(logs); // ["Sub1: Hello", "Sub2: Hello", "Sub2: World"]

// Clear all
observable.clear();
observable.notify("Ignored");
console.log(logs.length); // 3 (unchanged)

// Error handling
observable.subscribe(() => {
  throw new Error("Test error");
});
observable.notify("Error test"); // Should not crash

Answer: ["Sub1: Hello", "Sub2: Hello"] ["Sub1: Hello", "Sub2: Hello", "Sub2: World"] 3 Error in subscriber: Error: Test error

OBSERVER PATTERN: Publish-subscribe pattern for event-driven architecture. IMPLEMENTATION DETAILS: 1. Store subscribers in Set (unique, O(1) operations) 2. Subscribe adds callback, returns unsubscribe function 3. Notify iterates through all callbacks 4. Error handling prevents crash from faulty subscribers 5. Clear method removes all subscribers WHY SET INSTEAD OF ARRAY: - Prevents duplicate subscriptions - Faster removal (O(1) vs O(n)) - Automatic uniqueness - Maintains insertion order ERROR HANDLING: - Wrap callback execution in try-catch - Log errors but continue notifying others - Critical for production systems MEMORY MANAGEMENT: - Return cleanup function from subscribe - Weak references alternative (WeakMap/WeakSet) - Clear method for bulk cleanup REAL-WORLD APPLICATIONS: - React state management - Redux store subscribers - DOM event system - WebSocket message handlers - Custom event emitters - Vue reactivity system RXJS COMPARISON: - Simplified version of RxJS Observable - Missing operators (map, filter, etc.) - No completion/error channels - No multicasting ASKED AT: Meta, Netflix, Microsoft (system design)

observablepatternseventspub-sub

18. Maximum Subarray Sum (Kadane's Algorithm)

intermediate

Given an array of integers, find the contiguous subarray with the largest sum. Return the sum. Example: maxSubArray([-2,1,-3,4,-1,2,1,-5,4]) // 6 Explanation: [4,-1,2,1] has sum 6 Implement using Kadane's algorithm with O(n) time and O(1) space.

javascript
function maxSubArray(nums) {
  let maxCurrent = nums[0];
  let maxGlobal = nums[0];
  
  for (let i = 1; i < nums.length; i++) {
    // Either extend the subarray or start new
    maxCurrent = Math.max(nums[i], maxCurrent + nums[i]);
    
    // Update global maximum
    if (maxCurrent > maxGlobal) {
      maxGlobal = maxCurrent;
    }
  }
  
  return maxGlobal;
}

// Test cases
console.log(maxSubArray([-2, 1, -3, 4, -1, 2, 1, -5, 4])); // 6
console.log(maxSubArray([1])); // 1
console.log(maxSubArray([5, 4, -1, 7, 8])); // 23
console.log(maxSubArray([-1, -2, -3])); // -1
console.log(maxSubArray([-2, -3, 4, -1, -2, 1, 5, -3])); // 7

// Extended: Return subarray indices
function maxSubArrayIndices(nums) {
  let maxCurrent = nums[0];
  let maxGlobal = nums[0];
  let start = 0, end = 0, tempStart = 0;
  
  for (let i = 1; i < nums.length; i++) {
    if (nums[i] > maxCurrent + nums[i]) {
      maxCurrent = nums[i];
      tempStart = i;
    } else {
      maxCurrent = maxCurrent + nums[i];
    }
    
    if (maxCurrent > maxGlobal) {
      maxGlobal = maxCurrent;
      start = tempStart;
      end = i;
    }
  }
  
  return { sum: maxGlobal, start, end, subarray: nums.slice(start, end + 1) };
}

console.log(maxSubArrayIndices([-2, 1, -3, 4, -1, 2, 1, -5, 4]));

Answer: 6 1 23 -1 7 { sum: 6, start: 3, end: 6, subarray: [4, -1, 2, 1] }

KADANE'S ALGORITHM: Dynamic programming solution for maximum subarray sum. ALGORITHM EXPLANATION: 1. Initialize maxCurrent and maxGlobal with first element 2. Iterate through array 3. For each element, choose: start new subarray or extend existing 4. Update maxCurrent = max(element, maxCurrent + element) 5. Update maxGlobal if maxCurrent is larger 6. Return maxGlobal TIME COMPLEXITY: O(n) - single pass SPACE COMPLEXITY: O(1) - constant extra space WHY IT WORKS: - Local maximum at each position - Either extend previous subarray or start fresh - Global maximum tracks best overall EDGE CASES: - All negative numbers: return least negative - Single element: return that element - All positive: return sum of entire array - Empty array: handle gracefully (not defined in problem) EXTENDED VERSION: - Track start and end indices - tempStart resets when starting new subarray - Useful for debugging and visualization REAL-WORLD APPLICATIONS: - Stock price analysis (best time to buy/sell) - Signal processing - Computer vision - Financial analysis - DNA sequence analysis ALTERNATIVE APPROACHES: - Divide and conquer: O(n log n) - Brute force: O(n²) - Dynamic programming: O(n) with O(n) space ASKED AT: Google, Amazon, Microsoft (algorithm rounds)

algorithmdynamic-programmingarrayskadane

19. Implement setTimeout Using setInterval

beginner

Implement setTimeout function using only setInterval. The function should execute a callback after specified delay, then clean up the interval. Example: mySetTimeout(() => { console.log("Hello after 1 second"); }, 1000);

javascript
function mySetTimeout(callback, delay) {
  const interval = setInterval(() => {
    callback();
    clearInterval(interval);
  }, delay);
}

// Alternative: Return cleanup function
function mySetTimeout2(callback, delay) {
  const interval = setInterval(() => {
    callback();
    clearInterval(interval);
  }, delay);
  
  return () => clearInterval(interval);
}

// Test
console.log("Start");
mySetTimeout(() => {
  console.log("1 second passed");
}, 1000);

mySetTimeout(() => {
  console.log("2 seconds passed");
}, 2000);

// With cleanup
const cleanup = mySetTimeout2(() => {
  console.log("This should not run");
}, 3000);

// Cancel before execution
cleanup();

// Verify it's cancelled
setTimeout(() => {
  console.log("End of test");
}, 3500);

Answer: Start 1 second passed 2 seconds passed End of test

IMPLEMENTATION CONCEPT: setInterval runs repeatedly, setTimeout runs once. SOLUTION: 1. Create interval that runs callback 2. Immediately clear interval after first execution 3. Returns one-time execution CLEANUP FUNCTION: - Return function to cancel timeout - Useful for React useEffect cleanup - Prevents memory leaks WHY THIS WORKS: - setInterval executes repeatedly - clearInterval stops execution - First execution happens after delay - Interval cleared immediately after PERFORMANCE CONSIDERATIONS: - Native setTimeout is more efficient - setInterval may have minor timing differences - Memory overhead of interval ID REAL-WORLD USE CASES: - Polyfill for environments missing setTimeout - Educational demonstration - Testing timer logic - Understanding event loop ALTERNATIVE IMPLEMENTATION: Using Promise: async function promiseTimeout(callback, delay) { await new Promise(resolve => setTimeout(resolve, delay)); callback(); } EDGE CASES: - Zero or negative delay - Callback that throws error - Multiple rapid timeouts - Memory leak prevention ASKED AT: Google, Meta (understanding timers)

setTimeoutsetIntervaltimersevent-loop

20. Remove Duplicates from Sorted Array In-Place

beginner

Given a sorted array, remove duplicates in-place such that each element appears only once. Return the new length. Do not allocate extra space for another array. Modify the input array in-place with O(1) extra memory. Example: const nums = [0,0,1,1,1,2,2,3,3,4]; const length = removeDuplicates(nums); // nums should be [0,1,2,3,4,...] // length should be 5

javascript
function removeDuplicates(nums) {
  if (nums.length === 0) return 0;
  
  let uniqueIndex = 0;
  
  for (let i = 1; i < nums.length; i++) {
    if (nums[i] !== nums[uniqueIndex]) {
      uniqueIndex++;
      nums[uniqueIndex] = nums[i];
    }
  }
  
  return uniqueIndex + 1;
}

// Test cases
let nums1 = [0, 0, 1, 1, 1, 2, 2, 3, 3, 4];
console.log("Length:", removeDuplicates(nums1));
console.log("Array:", nums1.slice(0, 5));

let nums2 = [1, 1, 2];
console.log("Length:", removeDuplicates(nums2));
console.log("Array:", nums2.slice(0, 2));

let nums3 = [];
console.log("Length:", removeDuplicates(nums3));

let nums4 = [1];
console.log("Length:", removeDuplicates(nums4));
console.log("Array:", nums4);

// Extended: Remove duplicates allowing at most 2 of each
function removeDuplicatesAtMostTwo(nums) {
  if (nums.length <= 2) return nums.length;
  
  let index = 2;
  
  for (let i = 2; i < nums.length; i++) {
    if (nums[i] !== nums[index - 2]) {
      nums[index] = nums[i];
      index++;
    }
  }
  
  return index;
}

let nums5 = [1, 1, 1, 2, 2, 3];
console.log("Allow two:", removeDuplicatesAtMostTwo(nums5));
console.log("Array:", nums5.slice(0, 5));

Answer: Length: 5 Array: [0, 1, 2, 3, 4] Length: 2 Array: [1, 2] Length: 0 Length: 1 Array: [1] Allow two: 5 Array: [1, 1, 2, 2, 3]

TWO-POINTER TECHNIQUE: Classic in-place array modification algorithm. ALGORITHM STEPS: 1. Handle empty array edge case 2. Initialize uniqueIndex pointer at 0 3. Iterate with i pointer starting at 1 4. When nums[i] != nums[uniqueIndex], increment uniqueIndex and copy 5. Return uniqueIndex + 1 (number of unique elements) TIME COMPLEXITY: O(n) - single pass SPACE COMPLEXITY: O(1) - in-place modification WHY IT WORKS: - Array is sorted, so duplicates are adjacent - uniqueIndex tracks last unique element - i finds next unique element - Copy unique elements to front EDGE CASES: - Empty array: return 0 - Single element: return 1 - All duplicates: return 1 - Already unique: return n EXTENDED VERSION: - Allow at most k duplicates - Generalize comparison to nums[index - k] - Useful for data compression REAL-WORLD APPLICATIONS: - Database duplicate removal - Data preprocessing - Image compression - Time series data cleaning - Sensor data filtering ALTERNATIVE APPROACHES: - Using Set (but O(n) space) - Filter method (creates new array) - Reduce (creates new array) ASKED AT: Google, Amazon, Meta (array manipulation)

arraystwo-pointerin-placeduplicates

21. Implement Promise.prototype.finally

intermediate

Implement your own version of Promise.prototype.finally that executes a callback when the promise settles (either resolves or rejects). Example: Promise.resolve(42) .finally(() => console.log("Cleanup")) .then(val => console.log(val)); // 42 Promise.reject("error") .finally(() => console.log("Cleanup")) .catch(err => console.log(err)); // "error"

javascript
Promise.prototype.myFinally = function(onFinally) {
  return this.then(
    value => Promise.resolve(onFinally()).then(() => value),
    reason => Promise.resolve(onFinally()).then(() => Promise.reject(reason))
  );
};

// Test cases
console.log("Test 1 - Resolved promise:");
Promise.resolve(42)
  .myFinally(() => {
    console.log("Cleanup executed");
  })
  .then(val => {
    console.log("Value:", val); // 42
  });

console.log("
Test 2 - Rejected promise:");
Promise.reject("error")
  .myFinally(() => {
    console.log("Cleanup executed on rejection");
  })
  .catch(err => {
    console.log("Error:", err); // "error"
  });

console.log("
Test 3 - Async cleanup:");
Promise.resolve("success")
  .myFinally(async () => {
    await new Promise(resolve => setTimeout(resolve, 100));
    console.log("Async cleanup done");
  })
  .then(val => {
    console.log("Final value:", val); // "success"
  });

console.log("
Test 4 - Finally returns promise:");
Promise.resolve(1)
  .myFinally(() => {
    return Promise.resolve(999); // Ignored
  })
  .then(val => {
    console.log("Value (not 999):", val); // 1
  });

Answer: Test 1 - Resolved promise: Cleanup executed Value: 42 Test 2 - Rejected promise: Cleanup executed on rejection Error: error Test 3 - Async cleanup: Async cleanup done Final value: success Test 4 - Finally returns promise: Value (not 999): 1

FINALLY METHOD BEHAVIOR: 1. Executes callback when promise settles (resolve or reject) 2. Returns new promise 3. Forwards original resolution value or rejection reason 4. Ignores return value from callback 5. If callback throws or returns rejected promise, propagates that error IMPLEMENTATION DETAILS: 1. Handle both then and catch paths 2. Wrap onFinally in Promise.resolve to handle async/sync callbacks 3. In then branch: execute cleanup, then return original value 4. In catch branch: execute cleanup, then rethrow original error 5. Proper error propagation if cleanup fails WHY PROMISE.RESOLVE: - Handles both synchronous and asynchronous callbacks - Converts any return value to promise - Consistent error handling EDGE CASES: - onFinally returns rejected promise: propagates new error - onFinally throws error: propagates error - onFinally returns value: value is ignored - Chain multiple finally calls REAL-WORLD USES: - Cleanup operations (close connections, clear timers) - Loading state management - Analytics tracking (success/failure) - Resource management ERROR PROPAGATION: If cleanup fails, that error overrides original promise outcome. This matches native finally behavior. ASKED AT: Google, Meta, Amazon (promise mastery)

promisesfinallyerror-handlingcleanup

22. Valid Parentheses with Multiple Types

beginner

Given a string containing just the characters "(", ")", "{", "}", "[" and "]", determine if the input string is valid. Valid conditions: 1. Open brackets must be closed by same type 2. Open brackets must be closed in correct order 3. Empty string is valid Examples: isValid("()") // true isValid("()[]{}") // true isValid("(]") // false isValid("([)]") // false isValid("{[]}") // true

javascript
function isValid(s) {
  const stack = [];
  const map = {
    ")": "(",
    "]": "[",
    "}": "{"
  };
  
  for (let char of s) {
    if (["(", "[", "{"].includes(char)) {
      stack.push(char);
    } else {
      const last = stack.pop();
      if (map[char] !== last) {
        return false;
      }
    }
  }
  
  return stack.length === 0;
}

// Alternative with switch
function isValid2(s) {
  const stack = [];
  
  for (let char of s) {
    switch (char) {
      case "(":
      case "[":
      case "{":
        stack.push(char);
        break;
      case ")":
        if (stack.pop() !== "(") return false;
        break;
      case "]":
        if (stack.pop() !== "[") return false;
        break;
      case "}":
        if (stack.pop() !== "{") return false;
        break;
    }
  }
  
  return stack.length === 0;
}

// Test cases
console.log(isValid("()")); // true
console.log(isValid("()[]{}")); // true
console.log(isValid("(]")); // false
console.log(isValid("([)]")); // false
console.log(isValid("{[]}")); // true
console.log(isValid("")); // true
console.log(isValid("((()))")); // true
console.log(isValid("((())")); // false

// Performance test
const testStr = "({[]})".repeat(10000);
console.time("Validation");
console.log("Large test valid:", isValid(testStr));
console.timeEnd("Validation");

Answer: true true false false true true true false Large test valid: true Validation: ~5ms

STACK-BASED SOLUTION: Classic algorithm for matching nested structures. ALGORITHM: 1. Initialize empty stack 2. Create mapping of closing to opening brackets 3. Iterate through each character 4. If opening bracket, push to stack 5. If closing bracket, check if matches top of stack 6. Return false if mismatch 7. At end, stack must be empty TIME COMPLEXITY: O(n) - single pass SPACE COMPLEXITY: O(n) - stack size WHY STACK WORKS: - Last opened bracket must be first closed (LIFO) - Stack naturally handles nesting - Easy to validate order EDGE CASES: - Empty string: valid - Single character: invalid - Only opening brackets: invalid - Only closing brackets: invalid - Mixed types with correct nesting: valid REAL-WORLD APPLICATIONS: - Code syntax validation (compilers) - HTML/XML tag matching - JSON validation - Calculator expression evaluation - Configuration file parsing EXTENSION QUESTIONS: - Add support for quotes and escapes - Handle comments (/* */, //) - Validate indentation levels - Check maximum depth OPTIMIZATIONS: - Early exit for odd length strings - Direct character comparison - Pre-allocated array for stack ASKED AT: Google, Amazon, Meta (very common)

stackparenthesesvalidationalgorithm

23. Find Missing Number in Array

beginner

Given an array containing n distinct numbers taken from 0, 1, 2, ..., n, find the one that is missing from the array. Examples: findMissing([3,0,1]) // 2 findMissing([9,6,4,2,3,5,7,0,1]) // 8 findMissing([0]) // 1 findMissing([0,1]) // 2 Solve with O(n) time and O(1) space.

javascript
function findMissing(nums) {
  const n = nums.length;
  let expectedSum = (n * (n + 1)) / 2;
  let actualSum = 0;
  
  for (let num of nums) {
    actualSum += num;
  }
  
  return expectedSum - actualSum;
}

// Alternative using XOR (avoids overflow)
function findMissingXOR(nums) {
  let missing = nums.length;
  
  for (let i = 0; i < nums.length; i++) {
    missing ^= i ^ nums[i];
  }
  
  return missing;
}

// Test cases
console.log(findMissing([3, 0, 1])); // 2
console.log(findMissing([9, 6, 4, 2, 3, 5, 7, 0, 1])); // 8
console.log(findMissing([0])); // 1
console.log(findMissing([0, 1])); // 2
console.log(findMissing([])); // 0

// Compare both methods
console.log("
XOR method:");
console.log(findMissingXOR([3, 0, 1])); // 2
console.log(findMissingXOR([9, 6, 4, 2, 3, 5, 7, 0, 1])); // 8

// Overflow test
const largeArray = [];
for (let i = 0; i <= 1000000; i++) {
  if (i !== 500000) largeArray.push(i);
}
console.log("
Large array missing (sum):", findMissing(largeArray));
console.log("Large array missing (XOR):", findMissingXOR(largeArray));

Answer: 2 8 1 2 0 XOR method: 2 8 Large array missing (sum): 500000 Large array missing (XOR): 500000

PROBLEM ANALYSIS: Array of n distinct numbers from 0 to n, missing one number. MATHEMATICAL APPROACH (SUM): 1. Calculate expected sum of 0..n: n(n+1)/2 2. Calculate actual sum of array 3. Missing = expected - actual TIME: O(n), SPACE: O(1) LIMITATION: - Integer overflow for large n - JavaScript numbers are 64-bit float, but still limited XOR APPROACH (BETTER): Property: a ^ a = 0 and a ^ 0 = a 1. Initialize missing = n 2. XOR missing with all indices and values 3. Result is missing number WHY XOR WORKS: - All numbers appear twice except missing one - XOR cancels duplicates - Example: 0^1^2 ^ 0^2 = 1 EDGE CASES: - Empty array: missing 0 - Single element [0]: missing 1 - [0,1]: missing 2 - Maximum n: handle large numbers REAL-WORLD APPLICATIONS: - Database record verification - Sequence number validation - Packet loss detection - Memory addressing ALTERNATIVE SOLUTIONS: - Sorting: O(n log n) time - Hash set: O(n) time, O(n) space - Binary search (if sorted): O(log n) OVERFLOW CONSIDERATIONS: - XOR avoids overflow completely - Sum approach works up to ~9e15 in JavaScript - XOR works for any size integers ASKED AT: Google, Amazon, Microsoft (algorithmic thinking)

arraysalgorithmxormath

24. Implement Async Task Queue with Concurrency Limit

advanced

Implement an AsyncQueue that processes tasks with a concurrency limit. Only N tasks can run simultaneously. Example: const queue = new AsyncQueue(2); // 2 concurrent tasks queue.add(() => fetch("/api/1")); queue.add(() => fetch("/api/2")); queue.add(() => fetch("/api/3")); // Only 2 run at once, third waits

javascript
class AsyncQueue {
  constructor(concurrency = 1) {
    this.concurrency = concurrency;
    this.running = 0;
    this.queue = [];
  }
  
  add(task) {
    return new Promise((resolve, reject) => {
      this.queue.push({
        task,
        resolve,
        reject
      });
      this.run();
    });
  }
  
  run() {
    while (this.running < this.concurrency && this.queue.length) {
      const { task, resolve, reject } = this.queue.shift();
      this.running++;
      
      Promise.resolve(task())
        .then(resolve)
        .catch(reject)
        .finally(() => {
          this.running--;
          this.run();
        });
    }
  }
}

// Test with simulated async tasks
const queue = new AsyncQueue(2);
const results = [];
const delays = [100, 200, 300, 400, 500];

console.log("Starting tasks with concurrency 2...");

const startTime = Date.now();

// Add 5 tasks with different delays
delays.forEach((delay, i) => {
  queue.add(() => {
    return new Promise(resolve => {
      setTimeout(() => {
        const elapsed = Date.now() - startTime;
        console.log(`Task ${i} completed after ${elapsed}ms`);
        resolve(`Result ${i}`);
      }, delay);
    });
  }).then(result => {
    results.push(result);
  });
});

// Check final results after all complete
setTimeout(() => {
  console.log("All results:", results);
  console.log("Expected: Tasks 0 and 1 start immediately");
  console.log("Then task 2 starts when one completes, etc.");
}, 1000);

Answer: Starting tasks with concurrency 2... Task 0 completed after ~100ms Task 1 completed after ~200ms Task 2 completed after ~400ms Task 3 completed after ~600ms Task 4 completed after ~900ms All results: ["Result 0", "Result 1", "Result 2", "Result 3", "Result 4"] Expected: Tasks 0 and 1 start immediately Then task 2 starts when one completes, etc.

ASYNC QUEUE PATTERN: Manages concurrent execution of asynchronous tasks. IMPLEMENTATION DETAILS: 1. Track running count and pending queue 2. Add task returns promise 3. Store task with resolve/reject callbacks 4. Run tasks while under concurrency limit 5. Decrement running count on completion 6. Continue processing queue KEY COMPONENTS: - Concurrency limit: max simultaneous tasks - Queue: pending tasks - Running counter: active tasks - Automatic continuation: check queue on completion PROMISE MANAGEMENT: - Wrap task in Promise.resolve for consistency - Chain then/catch to propagate results - Use finally for cleanup REAL-WORLD USES: - API rate limiting - Database connection pooling - File upload queues - Image processing pipelines - Web scraping concurrency control PERFORMANCE CONSIDERATIONS: - Avoid recursion for large queues - Memory efficient (store only needed data) - Non-blocking execution EXTENSION FEATURES: - Priority queue - Task cancellation - Progress tracking - Error recovery strategies - Timeout handling CONCURRENCY PATTERNS: - Sequential: concurrency = 1 - Parallel: concurrency = Infinity - Controlled: fixed concurrency ASKED AT: Netflix, Uber, Stripe (systems design)

asyncqueueconcurrencypromises

25. String Compression (Run-Length Encoding)

beginner

Implement a basic string compression using counts of repeated characters. If compressed string is not smaller, return original. Example: compress("aabcccccaaa") // "a2b1c5a3" compress("abc") // "abc" (compressed "a1b1c1" is longer) compress("aabbcc") // "a2b2c2" (same length, return compressed) Characters can be any ASCII character.

javascript
function compress(str) {
  if (!str.length) return str;
  
  let compressed = [];
  let count = 1;
  let currentChar = str[0];
  
  for (let i = 1; i <= str.length; i++) {
    if (i < str.length && str[i] === currentChar) {
      count++;
    } else {
      compressed.push(currentChar + count);
      
      if (i < str.length) {
        currentChar = str[i];
        count = 1;
      }
    }
  }
  
  const compressedStr = compressed.join("");
  return compressedStr.length < str.length ? compressedStr : str;
}

// Test cases
console.log(compress("aabcccccaaa")); // "a2b1c5a3"
console.log(compress("abc")); // "abc"
console.log(compress("aabbcc")); // "a2b2c2"
console.log(compress("")); // ""
console.log(compress("a")); // "a"
console.log(compress("aa")); // "aa" (a2 same length)
console.log(compress("aaa")); // "a3"
console.log(compress("aaaaaaaaaa")); // "a10"

// Edge case: numbers in string
console.log(compress("111122223333")); // "14243343"

// Performance test
const longStr = "a".repeat(1000) + "b".repeat(1000);
console.time("Compression");
const result = compress(longStr);
console.timeEnd("Compression");
console.log("Compression ratio:", result.length / longStr.length);

Answer: a2b1c5a3 abc a2b2c2 a aa a3 a10 14243343 Compression: ~0.1ms Compression ratio: 0.004

RUN-LENGTH ENCODING: Simple compression algorithm for repeated characters. ALGORITHM: 1. Handle empty string edge case 2. Track current character and count 3. Iterate through string 4. When character changes, append char+count to result 5. Compare compressed vs original length 6. Return shorter version TIME COMPLEXITY: O(n) - single pass SPACE COMPLEXITY: O(n) - compressed string storage OPTIMIZATION: - Pre-check if compression can help (all unique chars) - Build string instead of array (but array.join is faster) - Calculate final length first to avoid building if longer EDGE CASES: - Empty string - Single character - All unique characters - Very long runs (count > 9 needs careful handling) - Numbers in input string - Case sensitivity REAL-WORLD APPLICATIONS: - Image compression (BMP, PCX formats) - Fax transmission - DNA sequence compression - Log file compression - Simple data encoding IMPROVEMENTS: - Handle counts > 9 properly (our version handles) - Two-pass to check length first - In-place modification if possible - Unicode character support ALTERNATIVE COMPRESSION: - Dictionary-based (LZ) - Huffman coding - Burrows-Wheeler transform ASKED AT: Google, Amazon, Microsoft (string algorithms)

stringscompressionalgorithmencoding

26. Find All Anagrams in a String

intermediate

Given a string s and a non-empty string p, find all start indices of p's anagrams in s. Anagram: permutation of letters (same letters, different order) Example: findAnagrams("cbaebabacd", "abc") // [0, 6] Explanation: - Substring at 0: "cba" is anagram of "abc" - Substring at 6: "bac" is anagram of "abc"

javascript
function findAnagrams(s, p) {
  const result = [];
  const pLen = p.length;
  const sLen = s.length;
  
  if (sLen < pLen) return result;
  
  // Create frequency maps
  const pCount = new Array(26).fill(0);
  const sCount = new Array(26).fill(0);
  
  // Helper to get char code index
  const getIndex = (char) => char.charCodeAt(0) - "a".charCodeAt(0);
  
  // Initialize counts for first window
  for (let i = 0; i < pLen; i++) {
    pCount[getIndex(p[i])]++;
    sCount[getIndex(s[i])]++;
  }
  
  // Compare initial window
  if (arraysEqual(pCount, sCount)) {
    result.push(0);
  }
  
  // Slide window through s
  for (let i = pLen; i < sLen; i++) {
    // Remove leftmost character
    sCount[getIndex(s[i - pLen])]--;
    // Add new character
    sCount[getIndex(s[i])]++;
    
    // Compare
    if (arraysEqual(pCount, sCount)) {
      result.push(i - pLen + 1);
    }
  }
  
  return result;
}

// Helper to compare arrays
function arraysEqual(a, b) {
  for (let i = 0; i < a.length; i++) {
    if (a[i] !== b[i]) return false;
  }
  return true;
}

// Test cases
console.log(findAnagrams("cbaebabacd", "abc")); // [0, 6]
console.log(findAnagrams("abab", "ab")); // [0, 1, 2]
console.log(findAnagrams("aaaaaaaaaa", "aaaa")); // [0, 1, 2, 3, 4, 5, 6]
console.log(findAnagrams("abc", "def")); // []
console.log(findAnagrams("", "a")); // []
console.log(findAnagrams("a", "a")); // [0]

// Performance test
const longS = "a".repeat(10000) + "b".repeat(10000);
const longP = "a".repeat(100);
console.time("Anagram search");
const results = findAnagrams(longS, longP);
console.timeEnd("Anagram search");
console.log("Found", results.length, "anagrams");

Answer: [0, 6] [0, 1, 2] [0, 1, 2, 3, 4, 5, 6] [] [] [0] Anagram search: ~10ms Found 9901 anagrams

SLIDING WINDOW WITH FREQUENCY COUNTER: Efficient O(n) solution for anagram detection. ALGORITHM: 1. Create frequency arrays for pattern p and first window of s 2. Compare initial window 3. Slide window across s: - Remove left character - Add right character - Compare frequencies 4. Store matching indices TIME COMPLEXITY: O(n) where n = s.length SPACE COMPLEXITY: O(1) - fixed size arrays (26 letters) WHY IT WORKS: - Anagrams have identical character frequencies - Sliding window maintains frequency counts efficiently - Constant time window updates OPTIMIZATION: - Use arrays instead of objects for frequency - Early exit for impossible cases (s shorter than p) - Compare only changed frequencies EDGE CASES: - s shorter than p: empty result - Empty strings: handle gracefully - Case sensitivity (assume lowercase) - Unicode characters (ASCII only in this solution) REAL-WORLD APPLICATIONS: - Plagiarism detection - DNA sequence matching - Spell checking - Search engine indexing - Cryptography (anagram attacks) ALTERNATIVE APPROACHES: - Sort each substring: O(n * k log k) - Hash map with decrement/increment - Prime number multiplication (overflow risk) EXTENSION: - Find smallest window containing all characters - Group anagrams in array of strings - Case-insensitive matching - Multiple pattern search ASKED AT: Google, Meta, Amazon (string algorithms)

stringssliding-windowanagramsfrequency-counter

27. Implement Property Getters/Setters with Validation

intermediate

Create a User class with private email property. Implement getter and setter with validation: 1. Email must contain "@" 2. Setter should convert to lowercase 3. Getter should return masked email (e.g., j***@example.com) Example: const user = new User(); user.email = "John@Example.com"; console.log(user.email); // "j***@example.com" Try setting invalid email should throw error.

javascript
class User {
  constructor() {
    this._email = "";
  }
  
  get email() {
    if (!this._email) return "";
    const [local, domain] = this._email.split("@");
    const maskedLocal = local[0] + "***";
    return `${maskedLocal}@${domain}`.toLowerCase();
  }
  
  set email(value) {
    if (!value.includes("@")) {
      throw new Error("Invalid email: must contain @");
    }
    this._email = value.toLowerCase();
  }
  
  // Alternative: using private field (#email)
  // #email = "";
  // 
  // get email() {
  //   return this.#email;
  // }
  // 
  // set email(value) {
  //   if (!value.includes("@")) {
  //     throw new Error("Invalid email");
  //   }
  //   this.#email = value.toLowerCase();
  // }
}

// Test cases
const user = new User();

// Valid email
user.email = "John@Example.com";
console.log("Masked email:", user.email); // "j***@example.com"
console.log("Actual stored:", user._email); // "john@example.com"

// Another valid email
user.email = "ALICE@GMAIL.COM";
console.log("Masked:", user.email); // "a***@gmail.com"

// Invalid email (should throw)
try {
  user.email = "invalid-email";
} catch (error) {
  console.log("Error caught:", error.message);
}

// Empty email
user.email = "";
console.log("Empty:", user.email); // ""

// Getter doesn't affect storage
console.log("_email after getter:", user._email); // ""

Answer: Masked email: j***@example.com Actual stored: john@example.com Masked: a***@gmail.com Error caught: Invalid email: must contain @ Empty: _email after getter:

GETTERS/SETTERS IN JAVASCRIPT: Special methods that intercept property access. IMPLEMENTATION DETAILS: 1. Use get keyword for getter 2. Use set keyword for setter 3. Store actual value in _email (convention) 4. Validate in setter before storing 5. Transform in getter before returning PRIVATE FIELDS (MODERN): - Use #email for true privacy - Available in ES2022+ - Not accessible outside class - Better than _email convention VALIDATION LOGIC: 1. Check for @ symbol 2. Convert to lowercase 3. Throw error on invalid input 4. Handle empty string MASKING LOGIC: 1. Split email at @ 2. Take first character of local part 3. Append "***" 4. Combine with domain 5. Convert to lowercase BENEFITS: - Encapsulation (hide implementation) - Validation on assignment - Transformation on retrieval - Consistent interface REAL-WORLD USES: - Form validation - Data normalization - Security masking - Logging sensitive data - API response formatting ALTERNATIVE APPROACH: - Proxy object for dynamic validation - Object.defineProperty for ES5 - Decorators in TypeScript SECURITY CONSIDERATIONS: - Email validation is complex (use library) - Masking may leak information - Consider GDPR/PII compliance ASKED AT: Meta, Google, Amazon (OOP design)

getterssettersvalidationclasses

28. Implement Binary Search on Sorted Array

beginner

Implement binary search to find index of target in sorted array. Return -1 if not found. Requirements: - O(log n) time complexity - Iterative implementation - Handle empty array Example: const arr = [1, 3, 5, 7, 9, 11]; binarySearch(arr, 7) // 3 binarySearch(arr, 2) // -1

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

// Recursive version
function binarySearchRecursive(arr, target, left = 0, right = arr.length - 1) {
  if (left > right) return -1;
  
  const mid = Math.floor((left + right) / 2);
  
  if (arr[mid] === target) {
    return mid;
  } else if (arr[mid] < target) {
    return binarySearchRecursive(arr, target, mid + 1, right);
  } else {
    return binarySearchRecursive(arr, target, left, mid - 1);
  }
}

// Test cases
const sortedArray = [1, 3, 5, 7, 9, 11, 13, 15];

console.log("Iterative:");
console.log(binarySearch(sortedArray, 7)); // 3
console.log(binarySearch(sortedArray, 1)); // 0
console.log(binarySearch(sortedArray, 15)); // 7
console.log(binarySearch(sortedArray, 8)); // -1
console.log(binarySearch([], 5)); // -1
console.log(binarySearch([5], 5)); // 0

console.log("
Recursive:");
console.log(binarySearchRecursive(sortedArray, 7)); // 3
console.log(binarySearchRecursive(sortedArray, 1)); // 0
console.log(binarySearchRecursive(sortedArray, 15)); // 7
console.log(binarySearchRecursive(sortedArray, 8)); // -1

// Performance comparison
const largeArray = [];
for (let i = 0; i < 1000000; i++) largeArray.push(i);

console.time("Iterative search");
console.log("Found at:", binarySearch(largeArray, 999999));
console.timeEnd("Iterative search");

console.time("Recursive search");
console.log("Found at:", binarySearchRecursive(largeArray, 999999));
console.timeEnd("Recursive search");

Answer: Iterative: 3 0 7 -1 -1 0 Recursive: 3 0 7 -1 Found at: 999999 Iterative search: ~0.1ms Found at: 999999 Recursive search: ~0.2ms

BINARY SEARCH ALGORITHM: Divide and conquer search on sorted arrays. ITERATIVE IMPLEMENTATION: 1. Initialize left and right pointers 2. While left <= right 3. Calculate middle index 4. Compare middle element with target 5. Adjust search range based on comparison 6. Return index if found, -1 otherwise TIME COMPLEXITY: O(log n) SPACE COMPLEXITY: O(1) for iterative, O(log n) for recursive WHY MID CALCULATION: - Math.floor((left + right) / 2) - Prevents overflow: left + Math.floor((right - left) / 2) - Integer division EDGE CASES: - Empty array - Single element array - Target at boundaries - Target not present - Duplicate values (returns any matching index) RECURSIVE VS ITERATIVE: - Iterative: less memory, no stack overflow risk - Recursive: cleaner code, potential stack overflow - Both have same time complexity REAL-WORLD APPLICATIONS: - Dictionary lookups - Database indexing - Version control systems - Game AI (decision trees) - Autocomplete suggestions VARIATIONS: - Find first occurrence - Find last occurrence - Find closest element - Search in rotated sorted array - 2D binary search OPTIMIZATION: - Use bit shifting: mid = (left + right) >> 1 - Early exit for edge values - Interpolation search for uniform distributions ASKED AT: Google, Amazon, Meta (fundamental algorithm)

binary-searchalgorithmarrayssearch

29. Implement Event Emitter Class

intermediate

Implement an EventEmitter class with these methods: - on(event, callback): subscribe to event - off(event, callback): unsubscribe from event - emit(event, ...args): trigger event with arguments - once(event, callback): subscribe for one execution Example: const emitter = new EventEmitter(); emitter.on("message", (msg) => console.log(msg)); emitter.emit("message", "Hello"); // Logs: "Hello" emitter.off("message", callback);

javascript
class EventEmitter {
  constructor() {
    this.events = new Map();
  }
  
  on(event, callback) {
    if (!this.events.has(event)) {
      this.events.set(event, new Set());
    }
    this.events.get(event).add(callback);
    return this;
  }
  
  off(event, callback) {
    if (this.events.has(event)) {
      this.events.get(event).delete(callback);
      if (this.events.get(event).size === 0) {
        this.events.delete(event);
      }
    }
    return this;
  }
  
  emit(event, ...args) {
    if (this.events.has(event)) {
      // Copy set to avoid issues if callbacks modify during iteration
      const callbacks = Array.from(this.events.get(event));
      callbacks.forEach(callback => {
        try {
          callback(...args);
        } catch (error) {
          console.error(`Error in event handler for ${event}:`, error);
        }
      });
    }
    return this;
  }
  
  once(event, callback) {
    const onceCallback = (...args) => {
      callback(...args);
      this.off(event, onceCallback);
    };
    return this.on(event, onceCallback);
  }
}

// Test cases
const emitter = new EventEmitter();

// Basic on/emit
console.log("Test 1 - Basic event:");
emitter.on("test", (msg) => console.log("Received:", msg));
emitter.emit("test", "Hello World");

// Multiple subscribers
console.log("
Test 2 - Multiple subscribers:");
emitter.on("data", (x, y) => console.log(`Data1: ${x}, ${y}`));
emitter.on("data", (x, y) => console.log(`Data2: ${x * 2}, ${y * 2}`));
emitter.emit("data", 5, 10);

// Once
console.log("
Test 3 - Once:");
emitter.once("once", () => console.log("This should only fire once"));
emitter.emit("once");
emitter.emit("once"); // No output

// Off
console.log("
Test 4 - Off:");
const callback = () => console.log("Should be removed");
emitter.on("remove", callback);
emitter.off("remove", callback);
emitter.emit("remove"); // No output

// Error handling
console.log("
Test 5 - Error handling:");
emitter.on("error", () => { throw new Error("Test error"); });
emitter.on("error", () => console.log("Second handler still runs"));
emitter.emit("error");

Answer: Test 1 - Basic event: Received: Hello World Test 2 - Multiple subscribers: Data1: 5, 10 Data2: 10, 20 Test 3 - Once: This should only fire once Test 4 - Off: Test 5 - Error handling: Error in event handler for error: Error: Test error Second handler still runs

EVENT EMITTER PATTERN: Publish-subscribe implementation for event-driven programming. IMPLEMENTATION DETAILS: 1. Store events in Map (event name → Set of callbacks) 2. on: add callback to Set (unique) 3. off: remove callback from Set 4. emit: execute all callbacks with arguments 5. once: wrapper that auto-removes after execution WHY USE SET: - Prevents duplicate callbacks - Fast add/delete operations (O(1)) - Maintains insertion order ERROR HANDLING: - Wrap callback execution in try-catch - Continue emitting to other handlers - Prevent single bad handler from breaking system MEMORY MANAGEMENT: - Remove empty event sets - Return this for method chaining - Copy callbacks array before iteration (prevents modification issues) REAL-WORLD USES: - Node.js EventEmitter - DOM event system - Vue/React component communication - Game engine event systems - Microservices communication EXTENSION FEATURES: - Wildcard events ("*") - Priority levels - Async event handlers - Event namespaces - Max listeners warning ALTERNATIVE DESIGNS: - Observer pattern (similar) - Mediator pattern - Reactive programming (RxJS) PERFORMANCE: - O(1) for add/remove - O(n) for emit (n = listeners) - Memory efficient for sparse events ASKED AT: Meta, Netflix, Microsoft (system design)

eventsemitterpatternsdesign

30. Implement JSON.stringify (Simplified)

advanced

Implement a simplified version of JSON.stringify that handles: - Primitive types (string, number, boolean, null) - Arrays - Plain objects Ignore: functions, undefined, symbols, circular references, Date, RegExp Example: myStringify({name: "John", age: 30, tags: ["js", "react"]}) // Should return: '{"name":"John","age":30,"tags":["js","react"]}'

javascript
function myStringify(value) {
  // Handle primitive types
  if (value === null) {
    return "null";
  }
  
  if (typeof value === "string") {
    return `"${value}"`;
  }
  
  if (typeof value === "number" || typeof value === "boolean") {
    return String(value);
  }
  
  // Handle arrays
  if (Array.isArray(value)) {
    const items = value.map(item => myStringify(item));
    return `[${items.join(",")}]`;
  }
  
  // Handle objects
  if (typeof value === "object") {
    const pairs = [];
    
    for (const key in value) {
      if (value.hasOwnProperty(key)) {
        const val = value[key];
        // Skip functions and undefined
        if (typeof val !== "function" && val !== undefined) {
          pairs.push(`"${key}":${myStringify(val)}`);
        }
      }
    }
    
    return `{${pairs.join(",")}}`;
  }
  
  // For other types (function, undefined, symbol) return undefined
  return undefined;
}

// Test cases
console.log("Primitives:");
console.log(myStringify(null)); // "null"
console.log(myStringify("hello")); // ""hello""
console.log(myStringify(42)); // "42"
console.log(myStringify(true)); // "true"

console.log("
Arrays:");
console.log(myStringify([1, 2, 3])); // "[1,2,3]"
console.log(myStringify(["a", "b", null])); // "["a","b",null]"
console.log(myStringify([{x: 1}, {y: 2}])); // "[{"x":1},{"y":2}]"

console.log("
Objects:");
const obj = {
  name: "John",
  age: 30,
  active: true,
  tags: ["js", "react"],
  address: {
    city: "NYC",
    zip: 10001
  }
};
console.log(myStringify(obj));
// Expected: '{"name":"John","age":30,"active":true,"tags":["js","react"],"address":{"city":"NYC","zip":10001}}'

console.log("
Skipped values:");
const withSkipped = {
  func: () => {},
  undef: undefined,
  normal: "value"
};
console.log(myStringify(withSkipped)); // '{"normal":"value"}'

// Compare with native
console.log("
Comparison with native JSON.stringify:");
const testObj = {a: 1, b: "test"};
console.log("Custom:", myStringify(testObj));
console.log("Native:", JSON.stringify(testObj));

Answer: Primitives: null "hello" 42 true Arrays: [1,2,3] ["a","b",null] [{"x":1},{"y":2}] Objects: {"name":"John","age":30,"active":true,"tags":["js","react"],"address":{"city":"NYC","zip":10001}} Skipped values: {"normal":"value"} Comparison with native JSON.stringify: Custom: {"a":1,"b":"test"} Native: {"a":1,"b":"test"}

JSON.STRINGIFY SIMPLIFIED: Recursive serialization of JavaScript values to JSON string. IMPLEMENTATION STRATEGY: 1. Handle primitives: null, string, number, boolean 2. Arrays: recursively stringify each element 3. Objects: recursively stringify key-value pairs 4. Skip: functions, undefined, symbols STRING HANDLING: - Wrap strings in double quotes - Escape special characters (simplified version doesn't) - Native JSON.stringify escapes: ", \, /, \b, \f, \n, \r, \t RECURSION: - Base cases: primitives - Recursive cases: arrays and objects - Depth-first traversal LIMITATIONS OF SIMPLIFIED VERSION: - No circular reference detection - No special type handling (Date, RegExp) - No Unicode escaping - No pretty printing option - No replacer function support REAL-WORLD CHALLENGES: - Circular references cause infinite recursion - Large objects can cause stack overflow - Special number values: NaN, Infinity, -0 - BigInt not supported OPTIMIZATION: - Use iterative approach for deep structures - Pre-allocate string buffer - Handle common patterns efficiently ALTERNATIVE APPROACHES: - Use native JSON.stringify with replacer - Streaming JSON for large data - Custom serialization for classes SECURITY CONSIDERATIONS: - JSON injection attacks - Prototype pollution - Memory exhaustion ASKED AT: Google, Meta, Amazon (deep JavaScript)

jsonserializationrecursionstringify