Live Coding

Check if an Object is Empty

How to check if a JavaScript object is empty?

Explanation: An object is empty if it doesn't have any own enumerable properties. We can use Object.keys(obj) which returns an array of a given object's own enumerable property names. If the length of this array is 0, the object is empty.

function isEmpty(obj) { return Object.keys(obj).length === 0; } console.log(isEmpty({})); // true console.log(isEmpty({ a: 1 })); // false

Reverse a String

Write a function to reverse a given string.

Explanation: The simplest way is to convert the string into an array of characters, use the built-in reverse() method for arrays, and then join the characters back into a string.

function reverseString(str) { return str.split('').reverse().join(''); } console.log(reverseString('hello')); // 'olleh'

Palindrome Check

Write a function that checks if a given string is a palindrome.

Explanation: A palindrome is a word or phrase that reads the same forwards and backward. We can check this by reversing the string (ignoring case and non-alphanumeric characters for a more robust check, but here we do a simple check) and comparing it to the original.

function isPalindrome(str) { const reversed = str.split('').reverse().join(''); return str === reversed; } console.log(isPalindrome('racecar')); // true console.log(isPalindrome('hello')); // false

Find the Maximum Number in an Array

Write a function to find the largest number in an array of numbers.

Explanation: You can iterate through the array, keeping track of the largest number found so far. Alternatively, you can use the Math.max() function along with the spread syntax (...) to pass array elements as arguments.

function findMaxNumber(arr) { if (arr.length === 0) return undefined; // Or throw an error return Math.max(...arr); } console.log(findMaxNumber([1, 5, 2, 9, 3])); // 9

FizzBuzz

Write a program that prints numbers from 1 to n. But for multiples of three, print 'Fizz' instead of the number and for the multiples of five, print 'Buzz'. For numbers which are multiples of both three and five, print 'FizzBuzz'.

Explanation: This classic problem tests basic loop and conditional logic. You need to iterate from 1 to n and use the modulo operator (%) to check for divisibility by 3 and 5.

function fizzBuzz(n) { for (let i = 1; i <= n; i++) { if (i % 3 === 0 && i % 5 === 0) { console.log('FizzBuzz'); } else if (i % 3 === 0) { console.log('Fizz'); } else if (i % 5 === 0) { console.log('Buzz'); } else { console.log(i); } } } fizzBuzz(15);

Remove Duplicates from an Array

Write a function that removes duplicate elements from an array.

Explanation: A modern and concise way to achieve this is by using a Set. Sets only store unique values. You can convert the array to a Set and then back to an array.

function removeDuplicates(arr) { return [...new Set(arr)]; } console.log(removeDuplicates([1, 2, 2, 3, 4, 4, 5])); // [1, 2, 3, 4, 5]

Anagram Check

Write a function to check if two strings are anagrams of each other.

Explanation: Anagrams are words formed by rearranging the letters of another. To check, we can clean, sort, and compare the strings. Cleaning involves removing non-alphanumeric characters and converting to a consistent case.

function isAnagram(str1, str2) { const clean = (str) => str.replace(/[^a-z0-9]/gi, '').toLowerCase(); const sorted = (str) => clean(str).split('').sort().join(''); return sorted(str1) === sorted(str2); } console.log(isAnagram('listen', 'silent')); // true console.log(isAnagram('hello', 'world')); // false

Calculate Factorial

Write a function to calculate the factorial of a non-negative integer.

Explanation: The factorial (n!) of a number is the product of all positive integers less than or equal to n. We can calculate this iteratively by multiplying numbers from 1 up to n.

function factorial(n) { if (n < 0) return undefined; // Factorial is not defined for negative numbers if (n === 0) return 1; let result = 1; for (let i = 1; i <= n; i++) { result *= i; } return result; } console.log(factorial(5)); // 120

Sum All Numbers in an Array

Write a function that returns the sum of all numbers in an array.

Explanation: You can use the reduce method, which is ideal for accumulating a single value from an array. It takes a callback function and an initial value (0 for summation).

function sumArray(arr) { return arr.reduce((accumulator, currentValue) => accumulator + currentValue, 0); } console.log(sumArray([1, 2, 3, 4])); // 10

Flatten a Nested Array

Write a function to flatten a nested array. For simplicity, assume only one level of nesting.

Explanation: For a single level of nesting, you can use Array.prototype.concat() with the spread operator or the flat() method (ES2019).

function flattenArray(arr) { // Using flat() (simpler if ES2019+ is okay) // return arr.flat(); // Using reduce and concat return arr.reduce((acc, val) => acc.concat(val), []); } console.log(flattenArray([1, [2, 3], 4, [5]])); // [1, 2, 3, 4, 5]

Count Vowels in a String

Write a function that counts the number of vowels (a, e, i, o, u) in a given string.

Explanation: Iterate through the string (case-insensitively) and check if each character is a vowel. Maintain a counter.

function countVowels(str) { const vowels = 'aeiou'; let count = 0; for (let char of str.toLowerCase()) { if (vowels.includes(char)) { count++; } } return count; } console.log(countVowels('Hello World')); // 3

Title Case a Sentence

Write a function that converts a sentence into title case (each word's first letter capitalized).

Explanation: Split the sentence into words, then iterate through each word, capitalizing the first letter and making the rest lowercase. Finally, join the words back.

function titleCase(str) { return str.toLowerCase().split(' ').map(word => { return word.charAt(0).toUpperCase() + word.slice(1); }).join(' '); } console.log(titleCase('i am a little tea pot')); // 'I Am A Little Tea Pot'

Two Sum Problem

Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.

Explanation: A common approach is to use a hash map (or a JavaScript object) to store numbers and their indices as you iterate. For each number, check if target - current_number exists in the map.

function twoSum(nums, target) { const map = {}; for (let i = 0; i < nums.length; i++) { const complement = target - nums[i]; if (map[complement] !== undefined) { return [map[complement], i]; } map[nums[i]] = i; } return []; // Or null, or throw error if no solution } console.log(twoSum([2, 7, 11, 15], 9)); // [0, 1]

Implement a Counter using Closures

Create a function that returns a counter function. Each time the returned function is called, it should increment and return a count.

Explanation: This demonstrates closures. The inner function has access to the count variable of the outer function's scope, even after the outer function has finished executing.

function createCounter() { let count = 0; return function() { count++; return count; }; } const counter1 = createCounter(); console.log(counter1()); // 1 console.log(counter1()); // 2 const counter2 = createCounter(); console.log(counter2()); // 1

Find the First Non-Repeating Character

Write a function that finds the first non-repeating character in a string.

Explanation: You can use a hash map to count character frequencies. First, iterate through the string to build the frequency map. Then, iterate through the string again and return the first character with a count of 1.

function firstNonRepeatingChar(str) { const charCount = {}; for (const char of str) { charCount[char] = (charCount[char] || 0) + 1; } for (const char of str) { if (charCount[char] === 1) { return char; } } return null; // Or some indicator if all repeat } console.log(firstNonRepeatingChar('aabbcdeeff')); // 'c' console.log(firstNonRepeatingChar('swiss')); // 'w'

Array Chunking

Write a function that splits an array into groups of a specified size.

Explanation: Iterate through the array, taking slices of the specified size and pushing them into a new array. Handle the case where the last chunk might be smaller.

function chunkArray(arr, size) { const chunked = []; let index = 0; while (index < arr.length) { chunked.push(arr.slice(index, index + size)); index += size; } return chunked; } console.log(chunkArray([1, 2, 3, 4, 5, 6, 7], 3)); // [[1, 2, 3], [4, 5, 6], [7]]

Check if a Number is Prime

Write a function to determine if a given number is a prime number.

Explanation: A prime number is a natural number greater than 1 that has no positive divisors other than 1 and itself. Iterate from 2 up to the square root of the number, checking for divisibility. Handle edge cases like 1 and 2.

function isPrime(num) { if (num <= 1) return false; if (num <= 3) return true; if (num % 2 === 0 || num % 3 === 0) return false; for (let i = 5; i * i <= num; i = i + 6) { if (num % i === 0 || num % (i + 2) === 0) return false; } return true; } console.log(isPrime(7)); // true console.log(isPrime(10)); // false

Find the Longest Word in a String

Write a function that finds the longest word in a sentence.

Explanation: Split the string into an array of words. Then, iterate through the array, keeping track of the longest word found so far (or its length).

function findLongestWord(str) { const words = str.split(' '); let longestWord = ''; for (const word of words) { // Clean words if needed (e.g., remove punctuation) if (word.length > longestWord.length) { longestWord = word; } } return longestWord; } console.log(findLongestWord('The quick brown fox jumped over the lazy dog')); // 'jumped'

Implement `Array.prototype.map`

Implement your own version of the Array.prototype.map function.

Explanation: The map function takes a callback and creates a new array by applying the callback to each element of the original array. Your function should iterate through the array and build the new array.

function myMap(arr, callback) { const mappedArray = []; for (let i = 0; i < arr.length; i++) { mappedArray.push(callback(arr[i], i, arr)); } return mappedArray; } const numbers = [1, 4, 9]; const roots = myMap(numbers, Math.sqrt); console.log(roots); // [1, 2, 3]

Implement `Array.prototype.filter`

Implement your own version of the Array.prototype.filter function.

Explanation: The filter function takes a callback and creates a new array with all elements that pass the test implemented by the provided callback function.

function myFilter(arr, callback) { const filteredArray = []; for (let i = 0; i < arr.length; i++) { if (callback(arr[i], i, arr)) { filteredArray.push(arr[i]); } } return filteredArray; } const words = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present']; const result = myFilter(words, word => word.length > 6); console.log(result); // ['exuberant', 'destruction', 'present']

Implement `Array.prototype.reduce`

Implement your own version of the Array.prototype.reduce function.

Explanation: The reduce function executes a user-supplied 'reducer' callback function on each element of the array, 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. Handle the initial value argument.

function myReduce(arr, callback, initialValue) { let accumulator = initialValue; let startIndex = 0; if (initialValue === undefined) { if (arr.length === 0) throw new TypeError('Reduce of empty array with no initial value'); accumulator = arr[0]; startIndex = 1; } for (let i = startIndex; i < arr.length; i++) { accumulator = callback(accumulator, arr[i], i, arr); } return accumulator; } const array1 = [1, 2, 3, 4]; const sum = myReduce(array1, (acc, curr) => acc + curr, 0); console.log(sum); // 10

Memoization - Fibonacci Sequence

Implement a Fibonacci function using memoization to optimize performance.

Explanation: The Fibonacci sequence involves repeated calculations. Memoization stores the results of expensive function calls and returns the cached result when the same inputs occur again.

function memoizedFib() { const cache = {}; function fib(n) { if (n in cache) { return cache[n]; } if (n <= 1) { return n; } const result = fib(n - 1) + fib(n - 2); cache[n] = result; return result; } return fib; } const fibonacci = memoizedFib(); console.log(fibonacci(10)); // 55 console.log(fibonacci(40)); // 102334155 (much faster than non-memoized)

Check for Balanced Brackets

Write a function to check if a string containing brackets {}[]() is balanced.

Explanation: Use a stack. When you encounter an opening bracket, push it onto the stack. When you encounter a closing bracket, check if the stack is empty or if the top element is the matching opening bracket. If it matches, pop the stack. If not, or if the stack is empty, it's unbalanced. At the end, an empty stack means balanced.

function isBalanced(str) { const stack = []; const map = { '(': ')', '{': '}', '[': ']' }; for (let char of str) { if (map[char]) { stack.push(char); } else if (Object.values(map).includes(char)) { if (stack.length === 0) return false; const lastOpen = stack.pop(); if (map[lastOpen] !== char) return false; } } return stack.length === 0; } console.log(isBalanced('({[]})')); // true console.log(isBalanced('([)]')); // false

Implement a Simple Queue

Implement a Queue data structure with enqueue (add to back) and dequeue (remove from front) methods.

Explanation: A queue follows the First-In, First-Out (FIFO) principle. An array can be used, with push for enqueue and shift for dequeue.

class Queue { constructor() { this.items = []; } enqueue(element) { this.items.push(element); } dequeue() { if (this.isEmpty()) return 'Underflow'; return this.items.shift(); } front() { if (this.isEmpty()) return 'No elements in Queue'; return this.items[0]; } isEmpty() { return this.items.length === 0; } } const q = new Queue(); q.enqueue(10); q.enqueue(20); console.log(q.dequeue()); // 10 console.log(q.front()); // 20

Implement a Simple Stack

Implement a Stack data structure with push (add to top) and pop (remove from top) methods.

Explanation: A stack follows the Last-In, First-Out (LIFO) principle. An array can be used, with push and pop methods.

class Stack { constructor() { this.items = []; } push(element) { this.items.push(element); } pop() { if (this.isEmpty()) return 'Underflow'; return this.items.pop(); } peek() { return this.items[this.items.length - 1]; } isEmpty() { return this.items.length === 0; } } const s = new Stack(); s.push(10); s.push(20); console.log(s.pop()); // 20 console.log(s.peek()); // 10

Find the Missing Number in Sequence

Given an array containing n distinct numbers taken from 0, 1, 2, ..., n, find the one that is missing from the array.

Explanation: The sum of numbers from 0 to n can be calculated using the formula n*(n+1)/2. The actual sum of the array elements can be calculated. The difference between these two sums will be the missing number.

function findMissingNumber(nums) { const n = nums.length; const expectedSum = n * (n + 1) / 2; const actualSum = nums.reduce((sum, num) => sum + num, 0); return expectedSum - actualSum; } console.log(findMissingNumber([3, 0, 1])); // 2 console.log(findMissingNumber([9, 6, 4, 2, 3, 5, 7, 0, 1])); // 8

Debounce Function

Implement a debounce function. Debouncing ensures that a function is not called again until a certain amount of time has passed without it being called.

Explanation: Use setTimeout and clearTimeout. Each time the debounced function is called, clear the previous timeout and set a new one. The actual function call happens only when the timeout completes.

function debounce(func, delay) { let timeoutId; return function(...args) { clearTimeout(timeoutId); timeoutId = setTimeout(() => { func.apply(this, args); }, delay); }; } // Example usage: const sayHello = () => console.log('Hello!'); const debouncedHello = debounce(sayHello, 1000); debouncedHello(); // Called after 1 sec (if not called again) debouncedHello(); // Resets timer debouncedHello(); // Resets timer, will actually run 1 sec after this call.

Throttle Function

Implement a throttle function. Throttling ensures that a function is called at most once in a specified time interval.

Explanation: Use a flag to indicate if a call is allowed. When called, if allowed, execute the function, set the flag to false, and use setTimeout to reset the flag after the interval.

function throttle(func, limit) { let inThrottle = false; return function(...args) { if (!inThrottle) { func.apply(this, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; } // Example usage: const logScroll = () => console.log('Scrolling...'); const throttledScroll = throttle(logScroll, 1000); // window.addEventListener('scroll', throttledScroll); // Will log at most once per second.

Currying Function

Write a function that takes a function with two arguments and returns a curried version of it.

Explanation: Currying transforms a function with multiple arguments into a sequence of functions, each taking a single argument. f(a, b) becomes f(a)(b).

function curry(fn) { return function(a) { return function(b) { return fn(a, b); }; }; } function add(a, b) { return a + b; } const curriedAdd = curry(add); const add5 = curriedAdd(5); console.log(add5(3)); // 8 console.log(curriedAdd(10)(20)); // 30

Deep Clone an Object

Write a function to perform a deep clone of a JavaScript object.

Explanation: A shallow clone copies only the top-level properties. A deep clone recursively copies all properties, including nested objects and arrays. A simple way (with limitations, e.g., doesn't handle functions, dates, regex well) is using JSON.parse(JSON.stringify(obj)). A recursive approach is more robust.

function deepClone(obj) { // Simple version (with limitations) try { return JSON.parse(JSON.stringify(obj)); } catch (e) { console.error("Cloning failed:", e); return null; } // More robust recursive (basic example): /* if (obj === null || typeof obj !== 'object') { return obj; } let clone = Array.isArray(obj) ? [] : {}; for (let key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { clone[key] = deepClone(obj[key]); } } return clone; */ } const original = { a: 1, b: { c: 2 } }; const cloned = deepClone(original); cloned.b.c = 3; console.log(original.b.c); // 2 (proves it's a deep clone) console.log(cloned.b.c); // 3

Get Object Keys

How to get an array of keys from an object?

Explanation: Use Object.keys(obj).

function getKeys(obj) { return Object.keys(obj); } console.log(getKeys({a: 1, b: 2})); // ['a', 'b']

Get Object Values

How to get an array of values from an object?

Explanation: Use Object.values(obj).

function getValues(obj) { return Object.values(obj); } console.log(getValues({a: 1, b: 2})); // [1, 2]

Check if Array Includes Value

How to check if an array contains a specific value?

Explanation: Use Array.prototype.includes(value).

function checkIncludes(arr, val) { return arr.includes(val); } console.log(checkIncludes([1, 2, 3], 2)); // true

Merge Two Arrays

How to merge two arrays into one?

Explanation: Use the spread syntax (...) or Array.prototype.concat().

function mergeArrays(arr1, arr2) { return [...arr1, ...arr2]; } console.log(mergeArrays([1, 2], [3, 4])); // [1, 2, 3, 4]

Explain 'this' in Global Scope

What does this refer to in the global scope in a browser?

Explanation: In the global execution context (outside any function), this refers to the global object, which is window in web browsers.

console.log(this === window); // true (in a browser environment)

Explain 'this' in an Object Method

What does this refer to when used inside an object method?

Explanation: When a function is called as a method of an object, this refers to the object the method is called on.

const myObject = { prop: 'Hello', greet() { return this.prop; } }; console.log(myObject.greet()); // 'Hello'

Explain 'this' with Arrow Functions

How do arrow functions handle this?

Explanation: Arrow functions do not have their own this context. Instead, they inherit this from the surrounding (lexical) scope where they are defined.

function MyClass() { this.value = 42; setTimeout(() => { console.log(this.value); // Logs 42 because 'this' is inherited }, 100); } new MyClass();

Difference between `let`, `const`, and `var`

What are the main differences between let, const, and var?

Explanation: var is function-scoped and hoisted. let and const are block-scoped and hoisted but are in a 'temporal dead zone' until declaration. const must be initialized and cannot be reassigned.

function scopeTest() { var a = 1; let b = 2; const c = 3; if (true) { var a = 10; // Re-declares and affects outer 'a' let b = 20; // New 'b' within block // const c = 30; // Would be a new 'c' console.log(a, b, c); // 10, 20, 3 } console.log(a, b, c); // 10, 2, 3 } scopeTest();

What is a Promise?

Explain what a Promise is in JavaScript.

Explanation: A Promise is an object representing the eventual completion (or failure) of an asynchronous operation and its resulting value. It can be in one of three states: pending, fulfilled, or rejected.

const myPromise = new Promise((resolve, reject) => { setTimeout(() => { resolve('Success!'); // reject('Error!'); }, 1000); }); myPromise .then(result => console.log(result)) .catch(error => console.error(error));

Using `async/await`

How do async and await simplify working with Promises?

Explanation: async/await provides syntactic sugar over Promises, making asynchronous code look and behave a bit more like synchronous code. An async function always returns a Promise, and await pauses execution until a Promise settles.

function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function run() { console.log('Starting...'); await delay(1000); console.log('Waited 1 second.'); await delay(500); console.log('Waited another 0.5 seconds.'); return 'Finished!'; } run().then(console.log);

Convert String to Number

How to convert a string to a number?

Explanation: Use parseInt(), parseFloat(), or the unary plus operator (+).

const str = '123.45'; console.log(parseInt(str)); // 123 console.log(parseFloat(str)); // 123.45 console.log(+str); // 123.45

Convert Number to String

How to convert a number to a string?

Explanation: Use String(), number.toString(), or string concatenation.

const num = 123; console.log(String(num)); // '123' console.log(num.toString()); // '123' console.log('' + num); // '123'

What is `JSON.stringify`?

What does JSON.stringify do?

Explanation: It converts a JavaScript object or value to a JSON string.

const obj = { name: 'Alice', age: 30 }; const jsonString = JSON.stringify(obj); console.log(jsonString); // '{"name":"Alice","age":30}'

What is `JSON.parse`?

What does JSON.parse do?

Explanation: It parses a JSON string, constructing the JavaScript value or object described by the string.

const jsonString = '{"name":"Alice","age":30}'; const obj = JSON.parse(jsonString); console.log(obj.name); // 'Alice'

Spread Operator in Arrays

How is the spread operator used with arrays?

Explanation: It allows an iterable (like an array) to be expanded in places where zero or more arguments or elements are expected. Useful for copying, merging, and adding elements.

const arr1 = [1, 2]; const arr2 = [3, 4]; const combined = [...arr1, ...arr2]; // [1, 2, 3, 4] const copy = [...arr1]; // [1, 2]

Spread Operator in Objects

How is the spread operator used with objects?

Explanation: It allows copying and merging object properties.

const obj1 = { a: 1, b: 2 }; const obj2 = { b: 3, c: 4 }; const merged = { ...obj1, ...obj2 }; // { a: 1, b: 3, c: 4 } const copy = { ...obj1 }; // { a: 1, b: 2 }

Destructuring Arrays

Explain array destructuring with an example.

Explanation: It's a syntax that makes it possible to unpack values from arrays into distinct variables.

const [a, b] = [10, 20]; console.log(a); // 10 console.log(b); // 20 const [x, , z] = [1, 2, 3]; console.log(z); // 3

Destructuring Objects

Explain object destructuring with an example.

Explanation: It makes it possible to unpack properties from objects into distinct variables.

const person = { name: 'Bob', age: 25 }; const { name, age } = person; console.log(name); // 'Bob' console.log(age); // 25 const { name: personName } = person; console.log(personName); // 'Bob'

Implement `call`

How could you implement a basic version of Function.prototype.call?

Explanation: call invokes a function with a specified this value and arguments provided individually. You can attach the function to the this context, call it, and then remove it.

Function.prototype.myCall = function(context, ...args) { context = context || window; const uniqueId = Symbol(); // Use a unique key context[uniqueId] = this; const result = context[uniqueId](...args); delete context[uniqueId]; return result; } function greet(greeting, punctuation) { console.log(`${greeting}, ${this.name}${punctuation}`); } greet.myCall({ name: 'Charlie' }, 'Hi', '!'); // Hi, Charlie!

Implement `apply`

How could you implement a basic version of Function.prototype.apply?

Explanation: apply is similar to call, but it accepts arguments as an array.

Function.prototype.myApply = function(context, argsArray) { context = context || window; const uniqueId = Symbol(); context[uniqueId] = this; const result = context[uniqueId](...(argsArray || [])); delete context[uniqueId]; return result; } function greet(greeting, punctuation) { console.log(`${greeting}, ${this.name}${punctuation}`); } greet.myApply({ name: 'David' }, ['Hello', '.']); // Hello, David.

Explain Event Loop

Briefly explain the JavaScript Event Loop.

Explanation: JavaScript is single-threaded, but it achieves concurrency using an event loop. The call stack handles synchronous code. Web APIs handle async operations (like setTimeout, fetch). When an async op finishes, its callback goes to the callback queue (or microtask queue for Promises). The event loop constantly checks if the call stack is empty; if it is, it moves the next callback from the queue to the stack for execution.

console.log('Start'); setTimeout(() => { console.log('Timeout Callback'); // Goes to Callback Queue }, 0); Promise.resolve().then(() => { console.log('Promise Resolved'); // Goes to Microtask Queue }); console.log('End'); // Output Order: Start, End, Promise Resolved, Timeout Callback // (Microtasks run before Macrotasks/Callbacks)

Binary Search

Implement a binary search function for a sorted array.

Explanation: Binary search efficiently finds an item in a sorted array by repeatedly dividing the search interval in half. If the value of the search key is less than the item in the middle of the interval, narrow the interval to the lower half. Otherwise, narrow it to the upper half. You do this until the value is found or the interval is empty.

function binarySearch(sortedArray, key) { let start = 0; let end = sortedArray.length - 1; while (start <= end) { let middle = Math.floor((start + end) / 2); if (sortedArray[middle] === key) { return middle; // Found } else if (sortedArray[middle] < key) { start = middle + 1; // Search right half } else { end = middle - 1; // Search left half } } return -1; // Not found } console.log(binarySearch([1, 3, 5, 7, 9, 11], 7)); // 3 console.log(binarySearch([1, 3, 5, 7, 9, 11], 2)); // -1

Merge Sort

Implement the Merge Sort algorithm.

Explanation: Merge Sort is a divide-and-conquer algorithm. It divides the input array into two halves, calls itself for the two halves, and then merges the two sorted halves. The merge function is crucial; it combines two sorted subarrays into one sorted array.

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) { let result = []; let leftIndex = 0; let rightIndex = 0; while (leftIndex < left.length && rightIndex < right.length) { if (left[leftIndex] < right[rightIndex]) { result.push(left[leftIndex]); leftIndex++; } else { result.push(right[rightIndex]); rightIndex++; } } return result.concat(left.slice(leftIndex)).concat(right.slice(rightIndex)); } console.log(mergeSort([38, 27, 43, 3, 9, 82, 10])); // [3, 9, 10, 27, 38, 43, 82]

Quick Sort

Implement the Quick Sort algorithm.

Explanation: Quick Sort is also a divide-and-conquer algorithm. It picks an element as a pivot and partitions the given array around the picked pivot. Elements smaller than the pivot go to the left, and elements greater go to the right. It then recursively sorts the subarrays.

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)]; } console.log(quickSort([10, 8, 2, 1, 6, 3, 9, 4, 7, 5])); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Bubble Sort

Implement the Bubble Sort algorithm.

Explanation: Bubble Sort is a simple sorting algorithm that repeatedly steps through the list, compares adjacent elements, and swaps them if they are in the wrong order. The pass through the list is repeated until the list is sorted.

function bubbleSort(arr) { let n = arr.length; let swapped; do { swapped = false; for (let i = 0; i < n - 1; i++) { if (arr[i] > arr[i + 1]) { [arr[i], arr[i + 1]] = [arr[i + 1], arr[i]]; // Swap swapped = true; } } n--; // Optimization: last element is already in place } while (swapped); return arr; } console.log(bubbleSort([64, 34, 25, 12, 22, 11, 90])); // [11, 12, 22, 25, 34, 64, 90]

Longest Substring Without Repeating Characters

Given a string, find the length of the longest substring without repeating characters.

Explanation: Use the 'sliding window' technique. Maintain a window (substring) and a set of characters in that window. Expand the window by moving the right pointer. If a repeating character is found, shrink the window from the left until the repeating character is removed.

function lengthOfLongestSubstring(s) { let maxLength = 0; let start = 0; const charMap = {}; for (let end = 0; end < s.length; end++) { const char = s[end]; if (charMap[char] >= start) { start = charMap[char] + 1; } charMap[char] = end; maxLength = Math.max(maxLength, end - start + 1); } return maxLength; } console.log(lengthOfLongestSubstring('abcabcbb')); // 3 ('abc') console.log(lengthOfLongestSubstring('bbbbb')); // 1 ('b') console.log(lengthOfLongestSubstring('pwwkew')); // 3 ('wke')

Implement a Linked List

Implement a Singly Linked List with add and print methods.

Explanation: A linked list is a linear data structure where elements are not stored at contiguous memory locations. Each element (node) points to the next. You need a Node class and a LinkedList class to manage the head and adding new nodes.

class Node { constructor(data, next = null) { this.data = data; this.next = next; } } class LinkedList { constructor() { this.head = null; } add(data) { const newNode = new Node(data); if (!this.head) { this.head = newNode; } else { let current = this.head; while (current.next) { current = current.next; } current.next = newNode; } } print() { let current = this.head; let list = ''; while (current) { list += current.data + ' -> '; current = current.next; } console.log(list + 'null'); } } const list = new LinkedList(); list.add(10); list.add(20); list.add(30); list.print(); // 10 -> 20 -> 30 -> null

Implement a Binary Search Tree (BST)

Implement a Binary Search Tree with insert and find methods.

Explanation: A BST is a node-based binary tree data structure which has the following properties: The left subtree of a node contains only nodes with keys lesser than the node’s key. The right subtree of a node contains only nodes with keys greater than the node’s key. Both subtrees must also be binary search trees.

class Node { constructor(data) { this.data = data; this.left = null; this.right = null; } } class BST { constructor() { this.root = null; } insert(data) { const newNode = new Node(data); if (!this.root) { this.root = newNode; return; } this._insertNode(this.root, newNode); } _insertNode(node, newNode) { if (newNode.data < node.data) { if (!node.left) node.left = newNode; else this._insertNode(node.left, newNode); } else { if (!node.right) node.right = newNode; else this._insertNode(node.right, newNode); } } find(data) { return this._findNode(this.root, data); } _findNode(node, data) { if (!node) return null; if (data < node.data) return this._findNode(node.left, data); else if (data > node.data) return this._findNode(node.right, data); else return node; } } const bst = new BST(); bst.insert(10); bst.insert(5); bst.insert(15); bst.insert(7); console.log(bst.find(7)); // Node { data: 7, left: null, right: null } console.log(bst.find(12)); // null

Rotate an Array

Write a function to rotate an array to the right by k steps.

Explanation: Rotating an array means moving its elements. For a right rotation, the last element becomes the first. Repeating this k times works but is inefficient. A better way is to use array manipulation or calculate the new positions.

function rotateArray(nums, k) { k = k % nums.length; // Handle cases where k > length if (k === 0) return nums; const partToMove = nums.splice(nums.length - k); nums.unshift(...partToMove); return nums; } console.log(rotateArray([1, 2, 3, 4, 5, 6, 7], 3)); // [5, 6, 7, 1, 2, 3, 4]

Find Intersection of Two Arrays

Given two arrays, write a function to compute their intersection (elements common to both).

Explanation: A good approach is to convert one array into a Set for O(1) average time lookups. Then, iterate through the second array and check if each element exists in the set. Collect the matches.

function intersection(nums1, nums2) { const set1 = new Set(nums1); const resultSet = new Set(); for (const num of nums2) { if (set1.has(num)) { resultSet.add(num); } } return [...resultSet]; } console.log(intersection([1, 2, 2, 1], [2, 2])); // [2] console.log(intersection([4, 9, 5], [9, 4, 9, 8, 4])); // [9, 4]

Group Anagrams

Given an array of strings, group anagrams together.

Explanation: The key is to find a unique signature for each group of anagrams. A common way is to sort each word alphabetically. Words that result in the same sorted string are anagrams. Use a hash map where keys are sorted words and values are arrays of original words.

function groupAnagrams(strs) { const map = {}; for (const str of strs) { const sortedStr = str.split('').sort().join(''); if (!map[sortedStr]) { map[sortedStr] = []; } map[sortedStr].push(str); } return Object.values(map); } console.log(groupAnagrams(['eat', 'tea', 'tan', 'ate', 'nat', 'bat'])); // Output: [['eat', 'tea', 'ate'], ['tan', 'nat'], ['bat']]

Move Zeroes to End

Given an array nums, write a function to move all 0's to the end of it while maintaining the relative order of the non-zero elements.

Explanation: Use a 'two-pointer' or a 'snowball' approach. One pointer keeps track of the position where the next non-zero element should go. Iterate through the array; if an element is non-zero, place it at the pointer's position and increment the pointer. Finally, fill the rest with zeros.

function moveZeroes(nums) { let nonZeroIndex = 0; // Move all non-zero elements to the front for (let i = 0; i < nums.length; i++) { if (nums[i] !== 0) { nums[nonZeroIndex] = nums[i]; nonZeroIndex++; } } // Fill the rest with zeros for (let i = nonZeroIndex; i < nums.length; i++) { nums[i] = 0; } return nums; } console.log(moveZeroes([0, 1, 0, 3, 12])); // [1, 3, 12, 0, 0]

Maximum Subarray (Kadane's Algorithm)

Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.

Explanation: Kadane's algorithm is an efficient way to solve this. Iterate through the array, keeping track of the currentMax sum ending at the current position and the globalMax sum found so far. If currentMax becomes negative, reset it to 0 (or rather, the current element).

function maxSubArray(nums) { let globalMax = -Infinity; let currentMax = 0; for (let i = 0; i < nums.length; i++) { currentMax = Math.max(nums[i], currentMax + nums[i]); if (currentMax > globalMax) { globalMax = currentMax; } } return globalMax; } console.log(maxSubArray([-2, 1, -3, 4, -1, 2, 1, -5, 4])); // 6 (from [4, -1, 2, 1])

Permutations of a String

Write a function to generate all permutations of a given string.

Explanation: This is typically solved using recursion and backtracking. For each character, fix it and recursively generate permutations for the rest of the string. Base case: when the string has only one character, return it.

function stringPermutations(str) { if (str.length === 0) return ['']; if (str.length === 1) return [str]; const results = []; for (let i = 0; i < str.length; i++) { const char = str[i]; const remainingChars = str.slice(0, i) + str.slice(i + 1); const perms = stringPermutations(remainingChars); for (const perm of perms) { results.push(char + perm); } } return [...new Set(results)]; // Use Set to handle duplicate chars if needed } console.log(stringPermutations('abc')); // ['abc', 'acb', 'bac', 'bca', 'cab', 'cba'] console.log(stringPermutations('aab')); // ['aab', 'aba', 'baa']

Greatest Common Divisor (GCD)

Write a function to find the Greatest Common Divisor (GCD) of two numbers.

Explanation: The Euclidean algorithm is an efficient method. If b is 0, a is the GCD. Otherwise, the GCD of a and b is the same as the GCD of b and a % b (the remainder of a divided by b).

function gcd(a, b) { while (b !== 0) { let temp = b; b = a % b; a = temp; } return a; } console.log(gcd(48, 18)); // 6 console.log(gcd(101, 103)); // 1

Least Common Multiple (LCM)

Write a function to find the Least Common Multiple (LCM) of two numbers.

Explanation: The LCM can be calculated using the GCD with the formula: LCM(a, b) = |a * b| / GCD(a, b).

function gcd(a, b) { // Helper from previous problem while (b !== 0) { let temp = b; b = a % b; a = temp; } return a; } function lcm(a, b) { if (a === 0 || b === 0) return 0; return Math.abs(a * b) / gcd(a, b); } console.log(lcm(15, 20)); // 60 console.log(lcm(7, 5)); // 35

Implement Promise.all

Implement a function that behaves like Promise.all.

Explanation: Promise.all takes an array of promises and returns a single promise that resolves when all input promises have resolved, or rejects if any input promise rejects. The resolved value is an array of the resolved values.

function myPromiseAll(promises) { return new Promise((resolve, reject) => { const results = []; let completedCount = 0; const numPromises = promises.length; if (numPromises === 0) { resolve([]); return; } promises.forEach((promise, index) => { Promise.resolve(promise) .then(value => { results[index] = value; completedCount++; if (completedCount === numPromises) { resolve(results); } }) .catch(reject); // Reject immediately on any error }); }); } // Example usage: const p1 = Promise.resolve(3); const p2 = 42; const p3 = new Promise((resolve) => setTimeout(resolve, 100, 'foo')); myPromiseAll([p1, p2, p3]).then(values => console.log(values)); // [3, 42, 'foo']

Invert Binary Tree

Write a function to invert a binary tree.

Explanation: To invert a binary tree, you swap the left and right children for every node. This can be done recursively or iteratively (using a queue or stack).

class Node { constructor(val, left = null, right = null) { this.val = val; this.left = left; this.right = right; } } function invertTree(root) { if (root === null) { return null; } // Swap the children [root.left, root.right] = [root.right, root.left]; // Recur for left and right children invertTree(root.left); invertTree(root.right); return root; } // Example: 4 -> [2, 7] -> [1, 3, 6, 9] // Becomes: 4 -> [7, 2] -> [9, 6, 3, 1]

Max Depth of Binary Tree

Given a binary tree, find its maximum depth.

Explanation: The maximum depth is the number of nodes along the longest path from the root node down to the farthest leaf node. This can be found recursively: MaxDepth = 1 + max(MaxDepth(left), MaxDepth(right)). The base case is when a node is null, its depth is 0.

class Node { constructor(val, left = null, right = null) { this.val = val; this.left = left; this.right = right; } } function maxDepth(root) { if (root === null) { return 0; } const leftDepth = maxDepth(root.left); const rightDepth = maxDepth(root.right); return Math.max(leftDepth, rightDepth) + 1; } // Example: 3 -> [9, 20] -> [null, null, 15, 7] const tree = new Node(3, new Node(9), new Node(20, new Node(15), new Node(7))); console.log(maxDepth(tree)); // 3

Best Time to Buy/Sell Stock

You are given an array prices where prices[i] is the price of a given stock on the ith day. You want to maximize your profit by choosing a single day to buy one stock and choosing a different day in the future to sell that stock. Return the maximum profit.

Explanation: Iterate through the prices, keeping track of the minimum price encountered so far (minPrice) and the maximum profit found so far (maxProfit). For each day, calculate the potential profit if you sold today (current price - minPrice) and update maxProfit if it's higher.

function maxProfit(prices) { let minPrice = Infinity; let maxProfit = 0; for (let i = 0; i < prices.length; i++) { if (prices[i] < minPrice) { minPrice = prices[i]; } else if (prices[i] - minPrice > maxProfit) { maxProfit = prices[i] - minPrice; } } return maxProfit; } console.log(maxProfit([7, 1, 5, 3, 6, 4])); // 5 (Buy at 1, Sell at 6) console.log(maxProfit([7, 6, 4, 3, 1])); // 0 (No profit possible)

Single Number

Given a non-empty array of integers nums, every element appears twice except for one. Find that single one.

Explanation: A very efficient way to solve this is using the XOR bitwise operator (^). XORing a number with itself results in 0. XORing a number with 0 results in the number itself. If you XOR all numbers in the array, the pairs will cancel out (become 0), leaving only the single number.

function singleNumber(nums) { let result = 0; for (const num of nums) { result ^= num; } return result; } console.log(singleNumber([2, 2, 1])); // 1 console.log(singleNumber([4, 1, 2, 1, 2])); // 4

Majority Element

Given an array nums of size n, return the majority element. The majority element is the element that appears more than ⌊n / 2⌋ times.

Explanation: The Boyer-Moore Voting Algorithm is an efficient way. Initialize a candidate and a count. Iterate through the array. If count is 0, set the current element as the candidate. If the current element matches the candidate, increment count; otherwise, decrement count.

function majorityElement(nums) { let candidate = null; let count = 0; for (const num of nums) { if (count === 0) { candidate = num; } count += (num === candidate) ? 1 : -1; } return candidate; } console.log(majorityElement([3, 2, 3])); // 3 console.log(majorityElement([2, 2, 1, 1, 1, 2, 2])); // 2

Climbing Stairs

You are climbing a staircase. It takes n steps to reach the top. Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?

Explanation: This is a classic dynamic programming problem, very similar to the Fibonacci sequence. The number of ways to reach step n is the sum of ways to reach step n-1 (by taking 1 step) and ways to reach step n-2 (by taking 2 steps). dp[n] = dp[n-1] + dp[n-2].

function climbStairs(n) { 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; } console.log(climbStairs(2)); // 2 (1+1, 2) console.log(climbStairs(3)); // 3 (1+1+1, 1+2, 2+1) console.log(climbStairs(4)); // 5

Product of Array Except Self

Given an integer array nums, return an array answer such that answer[i] is equal to the product of all the elements of nums except nums[i]. Do not use the division operator.

Explanation: You can solve this by calculating prefix products and suffix products. The result at index i is the product of all elements before i multiplied by the product of all elements after i.

function productExceptSelf(nums) { const n = nums.length; const answer = new Array(n).fill(1); // Calculate prefix products let prefix = 1; for (let i = 0; i < n; i++) { answer[i] = prefix; prefix *= nums[i]; } // Calculate suffix products and multiply with prefix let suffix = 1; for (let i = n - 1; i >= 0; i--) { answer[i] *= suffix; suffix *= nums[i]; } return answer; } console.log(productExceptSelf([1, 2, 3, 4])); // [24, 12, 8, 6]

Number of Islands

Given a 2D grid map of '1's (land) and '0's (water), count the number of islands. An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically.

Explanation: Iterate through each cell of the grid. If you find a '1' (land), increment the island count and start a Depth First Search (DFS) or Breadth First Search (BFS) from that cell. During the search, mark all connected '1's as '0' (or visited) to avoid recounting them.

function numIslands(grid) { if (!grid || grid.length === 0) return 0; let count = 0; const rows = grid.length; const cols = grid[0].length; function dfs(r, c) { if (r < 0 || c < 0 || r >= rows || c >= cols || 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 < rows; r++) { for (let c = 0; c < cols; c++) { if (grid[r][c] === '1') { count++; dfs(r, c); } } } return count; } const grid1 = [ ['1', '1', '1', '1', '0'], ['1', '1', '0', '1', '0'], ['1', '1', '0', '0', '0'], ['0', '0', '0', '0', '0'] ]; // console.log(numIslands(grid1)); // 1 (Need to run on a copy or restore grid)

LRU Cache

Design and implement a data structure for Least Recently Used (LRU) Cache. It should support get and put operations.

Explanation: An LRU cache evicts the least recently used item when capacity is reached. A common implementation uses a Map (or a hash map) for O(1) lookups and a Doubly Linked List for O(1) updates of 'recently used' status. JavaScript's Map maintains insertion order, which can simplify a basic implementation.

class LRUCache { constructor(capacity) { this.capacity = capacity; this.cache = new Map(); } get(key) { if (!this.cache.has(key)) return -1; const value = this.cache.get(key); this.cache.delete(key); // Remove to re-insert at the 'end' (most recent) this.cache.set(key, value); return value; } put(key, value) { if (this.cache.has(key)) { this.cache.delete(key); } else if (this.cache.size >= this.capacity) { const oldestKey = this.cache.keys().next().value; this.cache.delete(oldestKey); } this.cache.set(key, value); } } const cache = new LRUCache(2); cache.put(1, 1); cache.put(2, 2); console.log(cache.get(1)); // 1 cache.put(3, 3); // Evicts 2 console.log(cache.get(2)); // -1

Generate Parentheses

Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses.

Explanation: This is a classic backtracking problem. Maintain counts for open and close parentheses. You can add an open parenthesis if open < n. You can add a close parenthesis if close < open. The base case is when the string length reaches 2 * n.

function generateParenthesis(n) { const results = []; function backtrack(currentString, openCount, closeCount) { if (currentString.length === n * 2) { results.push(currentString); return; } if (openCount < n) { backtrack(currentString + '(', openCount + 1, closeCount); } if (closeCount < openCount) { backtrack(currentString + ')', openCount, closeCount + 1); } } backtrack('', 0, 0); return results; } console.log(generateParenthesis(3)); // ['((()))', '(()())', '(())()', '()(())', '()()()']

Container With Most Water

Given n non-negative integers a1, a2, ..., an , where each represents a point at coordinate (i, ai). n vertical lines are drawn such that the two endpoints of the line i is at (i, ai) and (i, 0). Find two lines, which, together with the x-axis forms a container, such that the container contains the most water.

Explanation: Use the two-pointer approach. Start with one pointer at the beginning and one at the end. Calculate the area. The area is limited by the shorter line. To potentially find a larger area, move the pointer pointing to the shorter line inwards.

function maxArea(height) { 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; } console.log(maxArea([1, 8, 6, 2, 5, 4, 8, 3, 7])); // 49

3Sum

Given an array nums of n integers, are there elements a, b, c in nums such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.

Explanation: First, sort the array. Then, iterate through the array with one pointer i. For each i, use two more pointers, left (starting at i+1) and right (starting at the end), to find two numbers that sum up to -nums[i]. Handle duplicates to ensure unique triplets.

function threeSum(nums) { nums.sort((a, b) => a - b); const results = []; for (let i = 0; i < nums.length - 2; i++) { if (i > 0 && nums[i] === nums[i - 1]) continue; // Skip duplicates for i let left = i + 1; let right = nums.length - 1; let target = -nums[i]; while (left < right) { let sum = nums[left] + nums[right]; if (sum === target) { results.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 < target) { left++; } else { right--; } } } return results; } console.log(threeSum([-1, 0, 1, 2, -1, -4])); // [[-1, -1, 2], [-1, 0, 1]]

Search Insert Position

Given a sorted array of distinct integers and a target value, return the index if the target is found. If not, return the index where it would be if it were inserted in order.

Explanation: This is a variation of binary search. Perform binary search, but if the element isn't found, the start pointer will end up at the position where the element should be inserted.

function searchInsert(nums, target) { let start = 0; let end = nums.length - 1; while (start <= end) { let mid = Math.floor((start + end) / 2); if (nums[mid] === target) { return mid; } else if (nums[mid] < target) { start = mid + 1; } else { end = mid - 1; } } return start; // If not found, start is the insertion point } console.log(searchInsert([1, 3, 5, 6], 5)); // 2 console.log(searchInsert([1, 3, 5, 6], 2)); // 1 console.log(searchInsert([1, 3, 5, 6], 7)); // 4

Merge Two Sorted Lists (Linked Lists)

Merge two sorted linked lists and return it as a new sorted list. The new list should be made by splicing together the nodes of the first two lists.

Explanation: Use a dummy head node to simplify the process. Use a pointer to build the new list. Compare the current nodes of both input lists and append the smaller one to the new list, advancing the pointer of that list. Repeat until one list is exhausted, then append the rest of the other list.

class ListNode { constructor(val = 0, next = null) { this.val = val; this.next = next; } } function mergeTwoLists(l1, l2) { let dummyHead = new ListNode(); let current = dummyHead; while (l1 !== null && l2 !== null) { if (l1.val < l2.val) { current.next = l1; l1 = l1.next; } else { current.next = l2; l2 = l2.next; } current = current.next; } current.next = l1 !== null ? l1 : l2; return dummyHead.next; } // Example: 1->2->4 and 1->3->4 => 1->1->2->3->4->4

Symmetric Tree

Given a binary tree, check whether it is a mirror of itself (i.e., symmetric around its center).

Explanation: This can be solved recursively. A tree is symmetric if the left subtree is a mirror image of the right subtree. Create a helper function isMirror(t1, t2) that checks if two trees are mirrors. It should verify: 1. t1.val === t2.val. 2. t1.left is a mirror of t2.right. 3. t1.right is a mirror of t2.left.

class Node { constructor(val, left = null, right = null) { this.val = val; this.left = left; this.right = right; } } function isSymmetric(root) { if (!root) return true; function isMirror(t1, t2) { if (!t1 && !t2) return true; if (!t1 || !t2 || t1.val !== t2.val) return false; return isMirror(t1.left, t2.right) && isMirror(t1.right, t2.left); } return isMirror(root.left, root.right); } // Example: 1 -> [2, 2] -> [3, 4, 4, 3] is symmetric.

Level Order Traversal (BST/Tree)

Given a binary tree, return the level order traversal of its nodes' values. (i.e., from left to right, level by level).

Explanation: This is a Breadth-First Search (BFS). Use a queue. Start by adding the root to the queue. While the queue is not empty, process all nodes at the current level. For each processed node, add its children (if they exist) to the queue for the next level.

class Node { constructor(val, left = null, right = null) { this.val = val; this.left = left; this.right = right; } } function levelOrder(root) { if (!root) return []; const result = []; const queue = [root]; while (queue.length > 0) { const levelSize = queue.length; const currentLevel = []; 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; } // Example: 3 -> [9, 20] -> [null, null, 15, 7] // Output: [[3], [9, 20], [15, 7]]

Convert Sorted Array to Height-Balanced BST

Given an array where elements are sorted in ascending order, convert it to a height-balanced binary search tree (BST).

Explanation: To create a height-balanced BST, you should pick the middle element of the array as the root. Then, recursively build the left subtree from the left half of the array and the right subtree from the right half.

class TreeNode { constructor(val = 0, left = null, right = null) { this.val = val; this.left = left; this.right = right; } } function sortedArrayToBST(nums) { if (!nums || nums.length === 0) return null; function buildBST(start, end) { if (start > end) return null; const mid = Math.floor((start + end) / 2); const node = new TreeNode(nums[mid]); node.left = buildBST(start, mid - 1); node.right = buildBST(mid + 1, end); return node; } return buildBST(0, nums.length - 1); } // Example: [-10, -3, 0, 5, 9] // Output: A tree like [0, -3, 9, -10, null, 5, null]

Implement `bind`

Implement your own version of Function.prototype.bind.

Explanation: bind 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. Use apply or call within a returned function, handling partial application (pre-set arguments).

Function.prototype.myBind = function(context, ...bindArgs) { const originalFunc = this; return function(...callArgs) { return originalFunc.apply(context, [...bindArgs, ...callArgs]); }; } const module = { x: 42, getX: function() { return this.x; } }; const unboundGetX = module.getX; console.log(unboundGetX()); // undefined (this is global/window) const boundGetX = unboundGetX.myBind(module); console.log(boundGetX()); // 42

Find the Kth Largest Element

Find the kth largest element in an unsorted array. Note that it is the kth largest element in the sorted order, not the kth distinct element.

Explanation: A simple approach is to sort the array and then pick the element at index n - k. A more efficient approach for large arrays involves using a min-heap or a selection algorithm like Quickselect.

function findKthLargest(nums, k) { // Simple approach: Sort nums.sort((a, b) => b - a); // Sort in descending order return nums[k - 1]; // Note: Quickselect would be more efficient in an interview // but sorting is easier to implement quickly. } console.log(findKthLargest([3, 2, 1, 5, 6, 4], 2)); // 5 console.log(findKthLargest([3, 2, 3, 1, 2, 4, 5, 5, 6], 4)); // 4

Check if Object has Property

How can you check if an object has a specific property?

Explanation: You can use the in operator (checks own and inherited properties), Object.prototype.hasOwnProperty.call(obj, prop) (checks only own properties), or simply obj.prop !== undefined (can be tricky with undefined values).

const person = { name: 'Eve', age: 28 }; function hasProp(obj, prop) { console.log(`Using 'in': ${prop in obj}`); console.log(`Using 'hasOwnProperty': ${Object.prototype.hasOwnProperty.call(obj, prop)}`); } hasProp(person, 'name'); // true, true hasProp(person, 'toString'); // true, false (toString is inherited)

Integer to Roman

Write a function to convert an integer to its Roman numeral representation.

Explanation: Create a mapping of Roman numerals and their corresponding values, ordered from largest to smallest. Iterate through this map. For each value, subtract it from the input number as many times as possible, appending the Roman numeral each time.

function intToRoman(num) { const map = { M: 1000, CM: 900, D: 500, CD: 400, C: 100, XC: 90, L: 50, XL: 40, X: 10, IX: 9, V: 5, IV: 4, I: 1 }; let result = ''; for (let key in map) { while (num >= map[key]) { result += key; num -= map[key]; } } return result; } console.log(intToRoman(3)); // III console.log(intToRoman(58)); // LVIII console.log(intToRoman(1994)); // MCMXCIV

Roman to Integer

Write a function to convert a Roman numeral to an integer.

Explanation: Create a map of Roman symbols to values. Iterate through the string. If the current symbol's value is less than the next symbol's value (like 'IV' or 'IX'), subtract the current value; otherwise, add it.

function romanToInt(s) { const map = { I: 1, V: 5, X: 10, L: 50, C: 100, D: 500, M: 1000 }; let result = 0; for (let i = 0; i < s.length; i++) { const currentVal = map[s[i]]; const nextVal = map[s[i + 1]]; if (nextVal > currentVal) { result -= currentVal; } else { result += currentVal; } } return result; } console.log(romanToInt('III')); // 3 console.log(romanToInt('LVIII')); // 58 console.log(romanToInt('MCMXCIV')); // 1994

Implement a `Set`

Implement a basic Set data structure (without using the built-in Set) with add, has, delete, and size methods.

Explanation: You can use a JavaScript object (hash map) as the underlying storage. Keys will be the set elements (you might need to handle how to store different types and ensure uniqueness as keys).

class MySet { constructor() { this.items = {}; this._size = 0; } add(element) { if (!this.has(element)) { this.items[element] = element; // Store the element this._size++; return true; } return false; } delete(element) { if (this.has(element)) { delete this.items[element]; this._size--; return true; } return false; } has(element) { return Object.prototype.hasOwnProperty.call(this.items, element); } get size() { return this._size; } } const set = new MySet(); set.add(1); set.add(2); console.log(set.has(1)); // true console.log(set.size); // 2 set.delete(1); console.log(set.has(1)); // false

Pascal's Triangle

Given an integer numRows, generate the first numRows of Pascal's triangle.

Explanation: In Pascal's triangle, each number is the sum of the two numbers directly above it. Start with [[1]]. For each subsequent row, start and end with 1. Each middle element is the sum of the element at the same index and the previous index from the row above.

function generatePascalsTriangle(numRows) { if (numRows === 0) return []; const triangle = [[1]]; for (let i = 1; i < numRows; i++) { const prevRow = triangle[i - 1]; const newRow = [1]; for (let j = 1; j < i; j++) { newRow.push(prevRow[j - 1] + prevRow[j]); } newRow.push(1); triangle.push(newRow); } return triangle; } console.log(generatePascalsTriangle(5)); // [[1], [1, 1], [1, 2, 1], [1, 3, 3, 1], [1, 4, 6, 4, 1]]

Word Search

Given a 2D board and a word, find if the word exists in the grid. The word can be constructed from letters of sequentially adjacent cells, where 'adjacent' cells are horizontally or vertically neighboring.

Explanation: This requires a Depth First Search (DFS) with backtracking. Iterate through each cell. If a cell matches the first letter of the word, start a DFS. The DFS function checks neighbors, ensuring they match the next letter and haven't been visited in the current path. If a path fails, backtrack by unmarking the cell.

function exist(board, word) { const rows = board.length; const cols = board[0].length; function dfs(r, c, index) { if (index === word.length) return true; // Word found if (r < 0 || c < 0 || r >= rows || c >= cols || board[r][c] !== word[index]) { return false; } const temp = board[r][c]; board[r][c] = '#'; // Mark as visited const found = dfs(r + 1, c, index + 1) || dfs(r - 1, c, index + 1) || dfs(r, c + 1, index + 1) || dfs(r, c - 1, index + 1); board[r][c] = temp; // Backtrack return found; } for (let r = 0; r < rows; r++) { for (let c = 0; c < cols; c++) { if (board[r][c] === word[0] && dfs(r, c, 0)) { return true; } } } return false; } const board = [['A','B','C','E'],['S','F','C','S'],['A','D','E','E']]; console.log(exist(board, 'ABCCED')); // true console.log(exist(board, 'SEE')); // true console.log(exist(board, 'ABCB')); // false

Minimum Window Substring

Given two strings s and t, find the minimum window in s which will contain all the characters in t. If there is no such window in s that covers all characters in t, return the empty string "".

Explanation: Use a sliding window approach with two pointers (left and right) and hash maps. One map stores the character counts needed from t. Another map stores the counts in the current window. Expand the window with right. Once the window contains all needed characters, try to shrink it with left to find the minimum possible window.

function minWindow(s, t) { if (!t || !s || s.length < t.length) return ""; const tMap = {}; for (const char of t) tMap[char] = (tMap[char] || 0) + 1; let required = Object.keys(tMap).length; let formed = 0; const windowMap = {}; let left = 0; let minLen = Infinity; let result = ""; for (let right = 0; right < s.length; right++) { const char = s[right]; windowMap[char] = (windowMap[char] || 0) + 1; if (tMap[char] && windowMap[char] === tMap[char]) { formed++; } while (left <= right && formed === required) { if (right - left + 1 < minLen) { minLen = right - left + 1; result = s.substring(left, right + 1); } const leftChar = s[left]; windowMap[leftChar]--; if (tMap[leftChar] && windowMap[leftChar] < tMap[leftChar]) { formed--; } left++; } } return result; } console.log(minWindow('ADOBECODEBANC', 'ABC')); // 'BANC'

Reverse Linked List

Given the head of a singly linked list, reverse the list, and return the reversed list.

Explanation: You need to iterate through the list, changing the next pointer of each node to point to the previous node. Keep track of the previous, current, and next nodes during iteration.

class ListNode { constructor(val = 0, next = null) { this.val = val; this.next = next; } } function reverseList(head) { let prev = null; let current = head; let next = null; while (current !== null) { next = current.next; // Store next node current.next = prev; // Reverse current node's pointer prev = current; // Move prev one step forward current = next; // Move current one step forward } return prev; // New head is the last 'prev' } // Example: 1->2->3->4->5 becomes 5->4->3->2->1

Detect Cycle in Linked List

Given head, the head of a linked list, determine if the linked list has a cycle in it.

Explanation: Use the 'Floyd's Tortoise and Hare' algorithm. Have two pointers, one moving one step at a time (slow) and one moving two steps at a time (fast). If there is a cycle, the fast pointer will eventually catch up to the slow pointer.

class ListNode { constructor(val = 0, next = null) { this.val = val; this.next = next; } } function hasCycle(head) { if (!head || !head.next) return false; let slow = head; let fast = head.next; while (slow !== fast) { if (!fast || !fast.next) return false; // Reached end, no cycle slow = slow.next; fast = fast.next.next; } return true; // Pointers met, cycle exists } // Example: 3->2->0->-4 with -4 pointing back to 2. hasCycle returns true.

Implement `Object.create`

Implement a function that mimics the behavior of Object.create(proto).

Explanation: Object.create creates a new object, using an existing object as the prototype of the newly created object. You can achieve this by creating a temporary constructor function, setting its prototype to the input proto, and then returning a new instance of it.

function myObjectCreate(proto) { if (typeof proto !== 'object' && typeof proto !== 'function') { if (proto !== null) { throw new TypeError('Object prototype may only be an Object or null: ' + proto); } } function F() {} F.prototype = proto; return new F(); } const person = { isHuman: false, printIntroduction: function() { console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`); } }; const me = myObjectCreate(person); me.name = 'Matthew'; me.isHuman = true; me.printIntroduction(); // My name is Matthew. Am I human? true console.log(Object.getPrototypeOf(me) === person); // true

What is Hoisting?

Explain hoisting in JavaScript and provide an example.

Explanation: Hoisting is JavaScript's default behavior of moving declarations to the top of the current scope (global or function) before code execution. Variable declarations (var) are hoisted and initialized with undefined. Function declarations are fully hoisted (both name and body). let and const are hoisted but not initialized, leading to a Temporal Dead Zone.

console.log(myVar); // undefined (var is hoisted and initialized with undefined) // console.log(myLet); // ReferenceError: Cannot access 'myLet' before initialization (TDZ) myFunc(); // 'Hello!' (Function declaration is fully hoisted) var myVar = 'I am a var'; let myLet = 'I am a let'; function myFunc() { console.log('Hello!'); }

Explain Event Bubbling and Capturing

Explain Event Bubbling and Capturing in the context of the DOM.

Explanation: These are two phases of event propagation in the HTML DOM. Capturing Phase: The event travels down from the window to the target element. Bubbling Phase: The event travels up from the target element back to the window. By default, most event handlers are registered during the bubbling phase. You can use addEventListener(type, listener, useCapture) with useCapture = true to handle events during the capturing phase.

// In an HTML structure: <div><p><span>Click Me</span></p></div> // If you click the <span>: // Capturing: window -> document -> html -> body -> div -> p -> span // Bubbling: span -> p -> div -> body -> html -> document -> window /* JS Example div.addEventListener('click', () => console.log('Div clicked'), true); // Capturing p.addEventListener('click', () => console.log('P clicked'), false); // Bubbling span.addEventListener('click', () => console.log('Span clicked'), false); // Bubbling // Output would be: Div clicked, Span clicked, P clicked */

Implement `JSON.parse` manually (basic)

Try to implement a very basic version of JSON.parse (handle simple objects, arrays, strings, numbers).

Explanation: This is a very complex task in full, but for a live coding setting, you might be asked to handle a very simplified subset. You would need to parse the string, identifying object {} and array [] boundaries, key-value pairs ("key": value), and basic data types. eval or new Function can cheat this but are dangerous. A real parser would use a lexer/tokenizer and parser.

function basicJsonParse(jsonString) { // WARNING: Using new Function is insecure like eval. // This is a simplified, INSECURE example for demonstration only. // A real implementation requires a proper parser. try { return (new Function('return ' + jsonString))(); } catch (e) { throw new SyntaxError('Invalid JSON: ' + e.message); } } console.log(basicJsonParse('{"a": 1, "b": ["x", "y"]}')); // { a: 1, b: ['x', 'y'] }

Flatten a Deeply Nested Array

Write a function to flatten a deeply nested array.

Explanation: Unlike the previous flatten (one level), this needs to handle any level of nesting. Recursion is a natural fit. Iterate through the array. If an element is an array, recursively call flatten on it and concatenate the result. Otherwise, add the element.

function deepFlatten(arr) { let flattened = []; arr.forEach(item => { if (Array.isArray(item)) { flattened = flattened.concat(deepFlatten(item)); } else { flattened.push(item); } }); return flattened; // ES2019+ provides a much simpler way: // return arr.flat(Infinity); } console.log(deepFlatten([1, [2, [3, 4], 5], 6])); // [1, 2, 3, 4, 5, 6]

Implement a Trie (Prefix Tree)

Implement a Trie data structure with insert, search, and startsWith methods.

Explanation: A Trie is a tree-like data structure used to efficiently store and retrieve keys in a dataset of strings. Each node represents a character, and paths from the root represent words or prefixes.

class TrieNode { constructor() { this.children = {}; this.isEndOfWord = false; } } class Trie { constructor() { this.root = new TrieNode(); } insert(word) { let node = this.root; for (const char of word) { if (!node.children[char]) { node.children[char] = new TrieNode(); } node = node.children[char]; } node.isEndOfWord = true; } search(word) { let node = this.root; for (const char of word) { if (!node.children[char]) return false; node = node.children[char]; } return node.isEndOfWord; } startsWith(prefix) { let node = this.root; for (const char of prefix) { if (!node.children[char]) return false; node = node.children[char]; } return true; } } const trie = new Trie(); trie.insert('apple'); console.log(trie.search('apple')); // true console.log(trie.search('app')); // false console.log(trie.startsWith('app')); // true

Shuffle an Array (Fisher-Yates)

Write a function to shuffle an array in place using the Fisher-Yates (or Knuth) algorithm.

Explanation: The Fisher-Yates algorithm iterates an array from the last element to the first. In each iteration, it swaps the current element with a randomly chosen element from the beginning of the array up to the current element (inclusive).

function shuffleArray(arr) { for (let i = arr.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [arr[i], arr[j]] = [arr[j], arr[i]]; // Swap elements } return arr; } console.log(shuffleArray([1, 2, 3, 4, 5])); // e.g., [3, 5, 1, 2, 4]

Compose Functions

Implement a compose function that takes multiple functions and returns a new function that applies them from right to left.

Explanation: Function composition (f ∘ g)(x) = f(g(x)) applies one function to the result of another. A general compose(f, g, h) would mean f(g(h(x))). Use reduceRight for an elegant implementation.

function compose(...funcs) { return function(initialArg) { return funcs.reduceRight((acc, func) => func(acc), initialArg); }; } const add5 = x => x + 5; const multiply3 = x => x * 3; const subtract2 = x => x - 2; const composedFunc = compose(subtract2, multiply3, add5); console.log(composedFunc(10)); // (10 + 5) * 3 - 2 = 15 * 3 - 2 = 45 - 2 = 43

Pipe Functions

Implement a pipe function that takes multiple functions and returns a new function that applies them from left to right.

Explanation: Similar to compose, but the application order is reversed: pipe(f, g, h) means h(g(f(x))). Use reduce for implementation.

function pipe(...funcs) { return function(initialArg) { return funcs.reduce((acc, func) => func(acc), initialArg); }; } const add5 = x => x + 5; const multiply3 = x => x * 3; const subtract2 = x => x - 2; const pipedFunc = pipe(add5, multiply3, subtract2); console.log(pipedFunc(10)); // (10 + 5) * 3 - 2 = 15 * 3 - 2 = 45 - 2 = 43

Coin Change Problem

Given an array of coin denominations and an amount, find the minimum number of coins to make that amount. Assume an infinite supply of each coin.

Explanation: This is a classic dynamic programming problem. Create an array dp where dp[i] stores the minimum coins needed for amount i. dp[i] = min(dp[i - coin]) + 1 for all coins.

function coinChange(coins, amount) { 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]; } console.log(coinChange([1, 2, 5], 11)); // 3 (5 + 5 + 1) console.log(coinChange([2], 3)); // -1

Lowest Common Ancestor (BST)

Given a binary search tree (BST), find the lowest common ancestor (LCA) of two given nodes in the BST.

Explanation: The LCA is the deepest node that has both given nodes as descendants. In a BST, you can find it by traversing from the root. If both nodes are smaller than the current node, go left. If both are larger, go right. If one is smaller and one is larger (or one is the current node), then the current node is the LCA.

class Node { constructor(val) { this.val = val; this.left = this.right = null; } } function lowestCommonAncestor(root, p, q) { let current = root; while (current) { if (p.val > current.val && q.val > current.val) { current = current.right; } else if (p.val < current.val && q.val < current.val) { current = current.left; } else { return current; // Found LCA } } return null; // Should not happen in a valid BST with p and q } // Example: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8 => LCA is 6

Serialize and Deserialize Binary Tree

Design an algorithm to serialize and deserialize a binary tree.

Explanation: Serialization converts a tree to a string or array. Deserialization reconstructs the tree. A common way is Pre-order Traversal (DFS). Use a special marker (e.g., '#') for null nodes to preserve the structure.

class Node { constructor(val) { this.val = val; this.left = this.right = null; } } function serialize(root) { const values = []; function dfs(node) { if (!node) { values.push('#'); return; } values.push(node.val); dfs(node.left); dfs(node.right); } dfs(root); return values.join(','); } function deserialize(data) { const values = data.split(','); let index = 0; function buildTree() { if (values[index] === '#') { index++; return null; } const node = new Node(parseInt(values[index])); index++; node.left = buildTree(); node.right = buildTree(); return node; } return buildTree(); } // Example: 1 -> [2, 3] -> [null, null, 4, 5] // Serialized: '1,2,#,#,3,4,#,#,5,#,#'

Implement `setInterval` with `setTimeout`

Implement a function mySetInterval that mimics setInterval but uses setTimeout recursively.

Explanation: setInterval runs a function repeatedly every N milliseconds. You can achieve this by having a function call itself with setTimeout after each execution. You also need a way to clear it (myClearInterval).

function mySetInterval(callback, delay, ...args) { const interval = { timerId: null }; function run() { interval.timerId = setTimeout(() => { callback.apply(this, args); run(); // Schedule the next call }, delay); } run(); return interval; // Return an object to allow clearing } function myClearInterval(interval) { clearTimeout(interval.timerId); } // Example usage: let count = 0; const intervalId = mySetInterval(() => { console.log(`Hello: ${++count}`); if (count === 3) myClearInterval(intervalId); }, 500);

Graph Traversal (BFS & DFS)

Implement Breadth-First Search (BFS) and Depth-First Search (DFS) for a given graph (adjacency list representation).

Explanation: BFS explores neighbors first (using a Queue), while DFS explores as far as possible along each branch (using a Stack or recursion). Keep track of visited nodes to avoid cycles and redundant work.

const graph = { A: ['B', 'C'], B: ['D'], C: ['E'], D: [], E: ['F'], F: [] }; function bfs(graph, startNode) { const queue = [startNode]; const visited = new Set(); const result = []; visited.add(startNode); while (queue.length > 0) { const node = queue.shift(); result.push(node); for (const neighbor of graph[node]) { if (!visited.has(neighbor)) { visited.add(neighbor); queue.push(neighbor); } } } return result; } function dfs(graph, startNode) { const stack = [startNode]; const visited = new Set(); const result = []; while (stack.length > 0) { const node = stack.pop(); if (!visited.has(node)) { visited.add(node); result.push(node); // Add neighbors in reverse to process them in order later (optional) for (let i = graph[node].length - 1; i >= 0; i--) { stack.push(graph[node][i]); } } } return result; } console.log('BFS:', bfs(graph, 'A')); // ['A', 'B', 'C', 'D', 'E', 'F'] console.log('DFS:', dfs(graph, 'A')); // ['A', 'B', 'D', 'C', 'E', 'F']

Rotate Image (Matrix)

You are given an n x n 2D matrix representing an image. Rotate the image by 90 degrees (clockwise) in-place.

Explanation: A common way to achieve this is by first transposing the matrix (swapping matrix[i][j] with matrix[j][i]) and then reversing each row.

function rotate(matrix) { const n = matrix.length; // Transpose for (let i = 0; i < n; i++) { for (let j = i; j < n; j++) { [matrix[i][j], matrix[j][i]] = [matrix[j][i], matrix[i][j]]; } } // Reverse each row for (let i = 0; i < n; i++) { matrix[i].reverse(); } return matrix; } const matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]; console.log(rotate(matrix)); // [[7, 4, 1], [8, 5, 2], [9, 6, 3]]

Spiral Matrix Traversal

Given an m x n matrix, return all elements of the matrix in spiral order.

Explanation: Use four pointers to define the boundaries: top, bottom, left, right. Traverse the top row, then the right column, then the bottom row, then the left column, shrinking the boundaries after each traversal, until left crosses right or top crosses bottom.

function spiralOrder(matrix) { if (!matrix || matrix.length === 0) return []; const rows = matrix.length, cols = matrix[0].length; const result = []; let top = 0, bottom = rows - 1, left = 0, right = cols - 1; while (top <= bottom && left <= right) { for (let i = left; i <= right; i++) result.push(matrix[top][i]); top++; for (let i = top; i <= bottom; i++) result.push(matrix[i][right]); right--; if (top <= bottom) { for (let i = right; i >= left; i--) result.push(matrix[bottom][i]); bottom--; } if (left <= right) { for (let i = bottom; i >= top; i--) result.push(matrix[i][left]); left++; } } return result; } const matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]; console.log(spiralOrder(matrix)); // [1, 2, 3, 6, 9, 8, 7, 4, 5]

Set Matrix Zeroes

Given an m x n matrix, if an element is 0, set its entire row and column to 0. Do it in-place.

Explanation: A naive approach requires O(m*n) extra space. To do it in O(1) space (or O(m+n)), you can use the first row and first column to store information about which rows/columns need to be zeroed. You need separate flags for whether the first row/column themselves need zeroing.

function setZeroes(matrix) { const rows = matrix.length, cols = matrix[0].length; let firstColZero = false; for (let r = 0; r < rows; r++) { if (matrix[r][0] === 0) firstColZero = true; for (let c = 1; c < cols; c++) { if (matrix[r][c] === 0) { matrix[r][0] = 0; matrix[0][c] = 0; } } } for (let r = rows - 1; r >= 0; r--) { for (let c = cols - 1; c >= 1; c--) { if (matrix[r][0] === 0 || matrix[0][c] === 0) { matrix[r][c] = 0; } } if (firstColZero) matrix[r][0] = 0; } return matrix; } // Example: [[1,1,1],[1,0,1],[1,1,1]] => [[1,0,1],[0,0,0],[1,0,1]]

Implement `Promise.race`

Implement a function that behaves like Promise.race.

Explanation: Promise.race takes an array of promises and returns a single promise. This returned promise settles (resolves or rejects) as soon as any of the input promises settles, with the value or reason from that promise.

function myPromiseRace(promises) { return new Promise((resolve, reject) => { if (!promises || promises.length === 0) { return; // Or resolve/reject depending on spec } promises.forEach(promise => { Promise.resolve(promise).then(resolve, reject); }); }); } const p1 = new Promise((resolve) => setTimeout(resolve, 500, 'one')); const p2 = new Promise((resolve) => setTimeout(resolve, 100, 'two')); myPromiseRace([p1, p2]).then(value => console.log(value)); // 'two'

Singleton Pattern

Implement the Singleton design pattern in JavaScript.

Explanation: The Singleton pattern ensures that a class has only one instance and provides a global point of access to it. This can be achieved using a closure to hold the instance.

const Singleton = (function() { let instance; function createInstance() { // Private methods and variables const privateVar = 'I am private'; function privateMethod() { console.log('Private'); } return { // Public methods and variables publicVar: 'I am public', publicMethod: function() { console.log('Public'); }, getPrivate: function() { return privateVar; } }; } return { getInstance: function() { if (!instance) { instance = createInstance(); } return instance; } }; })(); const instance1 = Singleton.getInstance(); const instance2 = Singleton.getInstance(); console.log(instance1 === instance2); // true console.log(instance1.getPrivate()); // 'I am private'

Validate IP Address

Write a function to check if a given string is a valid IPv4 or IPv6 address.

Explanation: For IPv4, check for 4 numbers (0-255) separated by dots, with no leading zeros (except for '0' itself). For IPv6, check for 8 groups of 1-4 hex digits separated by colons (handle variations like '::'). Regex can be used, but manual parsing is often clearer for interviews.

function validIPAddress(IP) { function isIPv4(s) { const parts = s.split('.'); if (parts.length !== 4) return false; for (const part of parts) { if (!/^[0-9]+$/.test(part)) return false; if (part.length > 1 && part.startsWith('0')) return false; const num = parseInt(part); if (num < 0 || num > 255) return false; } return true; } function isIPv6(s) { const parts = s.split(':'); if (parts.length !== 8) return false; // Simplified: No '::' for (const part of parts) { if (part.length < 1 || part.length > 4) return false; if (!/^[0-9a-fA-F]+$/.test(part)) return false; } return true; } if (IP.includes('.')) return isIPv4(IP) ? 'IPv4' : 'Neither'; if (IP.includes(':')) return isIPv6(IP) ? 'IPv6' : 'Neither'; return 'Neither'; } console.log(validIPAddress('172.16.254.1')); // IPv4 console.log(validIPAddress('2001:0db8:85a3:0000:0000:8a2e:0370:7334')); // IPv6 (Simplified) console.log(validIPAddress('256.256.256.256')); // Neither

Find Peak Element

A peak element is an element that is strictly greater than its neighbors. Given an input array nums, find a peak element and return its index.

Explanation: Since any peak will do, and nums[-1] and nums[n] are considered -Infinity, you can use a modified binary search. If nums[mid] is less than nums[mid+1], a peak must exist to the right. Otherwise, a peak must exist to the left (or mid itself is a peak).

function findPeakElement(nums) { let left = 0; let right = nums.length - 1; while (left < right) { let mid = Math.floor((left + right) / 2); if (nums[mid] < nums[mid + 1]) { left = mid + 1; // Peak is to the right } else { right = mid; // Peak is mid or to the left } } return left; // 'left' will be the index of a peak } console.log(findPeakElement([1, 2, 3, 1])); // 2 (index of 3) console.log(findPeakElement([1, 2, 1, 3, 5, 6, 4])); // 5 (index of 6) or 1 (index of 2)

Counting Bits

Given an integer n, return an array ans of length n + 1 such that for each i (0 <= i <= n), ans[i] is the number of 1's in the binary representation of i.

Explanation: You can solve this using dynamic programming. Notice the relationship: dp[i] = dp[i >> 1] + (i & 1). The number of 1s in i is the number of 1s in i shifted right (i.e., i/2), plus 1 if i is odd.

function countBits(n) { const dp = new Array(n + 1).fill(0); for (let i = 1; i <= n; i++) { dp[i] = dp[i >> 1] + (i & 1); // Or: dp[i] = dp[i & (i - 1)] + 1; (Removes last set bit) } return dp; } console.log(countBits(5)); // [0, 1, 1, 2, 1, 2]

Power of Two

Given an integer n, return true if it is a power of two. Otherwise, return false.

Explanation: A power of two in binary representation has exactly one '1' bit (e.g., 1=1, 2=10, 4=100, 8=1000). A clever bitwise trick is to check if n > 0 and (n & (n - 1)) === 0. If n is a power of two, n-1 will have all bits below that '1' set to '1'. ANDing them results in 0.

function isPowerOfTwo(n) { return n > 0 && (n & (n - 1)) === 0; } console.log(isPowerOfTwo(16)); // true console.log(isPowerOfTwo(1)); // true console.log(isPowerOfTwo(18)); // false

Merge Intervals

Given an array of intervals where intervals[i] = [starti, endi], merge all overlapping intervals, and return an array of the non-overlapping intervals.

Explanation: First, sort the intervals based on their start times. Then, iterate through the sorted intervals. If the current interval overlaps with the last interval in the result list, merge them by updating the end time of the last interval. Otherwise, add the current interval to the result list.

function mergeIntervals(intervals) { if (intervals.length === 0) return []; intervals.sort((a, b) => a[0] - b[0]); const merged = [intervals[0]]; for (let i = 1; i < intervals.length; i++) { const last = merged[merged.length - 1]; const current = intervals[i]; if (current[0] <= last[1]) { // Overlap: merge last[1] = Math.max(last[1], current[1]); } else { // No overlap: add new interval merged.push(current); } } return merged; } console.log(mergeIntervals([[1, 3], [2, 6], [8, 10], [15, 18]])); // [[1, 6], [8, 10], [15, 18]]

Word Break

Given a string s and a dictionary of strings wordDict, return true if s can be segmented into a space-separated sequence of one or more dictionary words.

Explanation: Use dynamic programming. Create a boolean array dp where dp[i] is true if the substring s[0...i-1] can be segmented. dp[i] is true if there exists a j < i such that dp[j] is true and the substring s[j...i-1] is in the dictionary.

function wordBreak(s, wordDict) { const wordSet = new Set(wordDict); const n = s.length; const dp = new Array(n + 1).fill(false); dp[0] = true; // Base case: empty string for (let i = 1; i <= n; i++) { for (let j = 0; j < i; j++) { if (dp[j] && wordSet.has(s.substring(j, i))) { dp[i] = true; break; } } } return dp[n]; } console.log(wordBreak('leetcode', ['leet', 'code'])); // true console.log(wordBreak('applepenapple', ['apple', 'pen'])); // true console.log(wordBreak('catsandog', ['cats', 'dog', 'sand', 'and', 'cat'])); // false

Implement `Array.prototype.flat`

Implement your own version of Array.prototype.flat, which creates a new array with all sub-array elements concatenated into it recursively up to a specified depth.

Explanation: Use recursion. Iterate through the array. If an element is an array and the current depth is less than the specified depth, recursively call flatten on it. Otherwise, push the element. Handle the default depth of 1.

function myFlat(arr, depth = 1) { let flattened = []; arr.forEach(item => { if (Array.isArray(item) && depth > 0) { flattened.push(...myFlat(item, depth - 1)); } else { flattened.push(item); } }); return flattened; } console.log(myFlat([1, [2, [3, 4], 5], 6])); // [2, [3, 4], 5] console.log(myFlat([1, [2, [3, 4], 5], 6], 2)); // [1, 2, 3, 4, 5, 6]

Reverse Words in a String

Given an input string s, reverse the order of the words.

Explanation: A 'word' is defined as a sequence of non-space characters. Trim the string, split it by one or more spaces, filter out any empty strings resulting from multiple spaces, reverse the array, and join it back with single spaces.

function reverseWords(s) { return s.trim().split(/\s+/).reverse().join(' '); } console.log(reverseWords('the sky is blue')); // 'blue is sky the' console.log(reverseWords(' hello world ')); // 'world hello' console.log(reverseWords('a good example')); // 'example good a'

Query String Parser

Write a function to parse a URL query string into an object.

Explanation: Split the string by &. For each part, split by = to get the key and value. Decode URI components for both key and value. Handle cases where a key might appear multiple times (create an array) or has no value.

function parseQueryString(query) { if (query.startsWith('?')) { query = query.substring(1); } const result = {}; if (!query) return result; query.split('&').forEach(pair => { const [key, value] = pair.split('='); const decodedKey = decodeURIComponent(key); const decodedValue = value !== undefined ? decodeURIComponent(value) : true; if (result.hasOwnProperty(decodedKey)) { if (Array.isArray(result[decodedKey])) { result[decodedKey].push(decodedValue); } else { result[decodedKey] = [result[decodedKey], decodedValue]; } } else { result[decodedKey] = decodedValue; } }); return result; } console.log(parseQueryString('?foo=bar&baz=qux&baz=quux&corge')); // { foo: 'bar', baz: ['qux', 'quux'], corge: true }

Use `Proxy` for Validation

Demonstrate how to use a Proxy object for object property validation.

Explanation: A Proxy allows you to intercept and customize operations performed on an object. Use the set trap to validate values before assigning them to a property. Throw an error if the validation fails.

const validator = { set: function(obj, prop, value) { if (prop === 'age') { if (!Number.isInteger(value)) { throw new TypeError('The age is not an integer'); } if (value < 0 || value > 150) { throw new RangeError('The age seems invalid'); } } // Default behavior: Set the property obj[prop] = value; return true; } }; const person = {}; const personProxy = new Proxy(person, validator); personProxy.age = 30; // OK console.log(personProxy.age); // 30 // personProxy.age = 'thirty'; // Throws TypeError // personProxy.age = 200; // Throws RangeError

Meeting Rooms

Given an array of meeting time intervals [[start1, end1], [start2, end2], ...], determine if a person could attend all meetings.

Explanation: If a person can attend all meetings, it means no two meetings overlap. To check this, sort the intervals by their start times. Then, iterate through the sorted intervals and check if the start time of the current meeting is before the end time of the previous meeting. If it is, there's an overlap, and the person cannot attend all.

function canAttendMeetings(intervals) { if (intervals.length < 2) return true; intervals.sort((a, b) => a[0] - b[0]); for (let i = 1; i < intervals.length; i++) { if (intervals[i][0] < intervals[i - 1][1]) { return false; // Overlap detected } } return true; } console.log(canAttendMeetings([[0, 30], [5, 10], [15, 20]])); // false (5<30) console.log(canAttendMeetings([[7, 10], [2, 4]])); // true

Implement `Promise.any`

Implement a function that behaves like Promise.any.

Explanation: Promise.any takes an array of promises and returns a single promise. This promise fulfills as soon as any of the input promises fulfill. If all input promises reject, it rejects with an AggregateError containing all rejection reasons.

function myPromiseAny(promises) { return new Promise((resolve, reject) => { const numPromises = promises.length; if (numPromises === 0) { reject(new AggregateError([], 'All promises were rejected')); return; } let rejectionCount = 0; const errors = []; promises.forEach((promise, index) => { Promise.resolve(promise) .then(resolve) // Resolve as soon as one fulfills .catch(error => { errors[index] = error; rejectionCount++; if (rejectionCount === numPromises) { reject(new AggregateError(errors, 'All promises were rejected')); } }); }); }); } // Example: myPromiseAny([Promise.reject('err1'), Promise.resolve('ok')]) -> 'ok' // Example: myPromiseAny([Promise.reject('err1'), Promise.reject('err2')]) -> AggregateError

Observer Pattern

Implement the Observer (Pub/Sub) design pattern in JavaScript.

Explanation: The Observer pattern defines a one-to-many dependency between objects. When one object (Subject) changes state, all its dependents (Observers) are notified and updated automatically. The Subject maintains a list of observers and provides methods to add, remove, and notify them.

class Subject { constructor() { this.observers = []; } subscribe(observer) { this.observers.push(observer); } unsubscribe(observer) { this.observers = this.observers.filter(obs => obs !== observer); } notify(data) { this.observers.forEach(observer => observer.update(data)); } } class Observer { constructor(name) { this.name = name; } update(data) { console.log(`${this.name} received: ${data}`); } } const subject = new Subject(); const obs1 = new Observer('Observer 1'); const obs2 = new Observer('Observer 2'); subject.subscribe(obs1); subject.subscribe(obs2); subject.notify('Something happened!'); // Observer 1 received: Something happened! // Observer 2 received: Something happened!

Find Duplicate Number

Given an array of integers nums containing n + 1 integers where each integer is in the range [1, n] inclusive, find the one repeated number.

Explanation: Since numbers are in [1, n] and the array size is n+1, it guarantees at least one duplicate. This can be mapped to a 'Find Cycle in Linked List' problem (Floyd's Tortoise and Hare). Treat the array values as pointers: index -> nums[index].

function findDuplicate(nums) { let tortoise = nums[0]; let hare = nums[0]; // Phase 1: Find the intersection point do { tortoise = nums[tortoise]; hare = nums[nums[hare]]; } while (tortoise !== hare); // Phase 2: Find the entrance to the cycle let ptr1 = nums[0]; let ptr2 = tortoise; while (ptr1 !== ptr2) { ptr1 = nums[ptr1]; ptr2 = nums[ptr2]; } return ptr1; } console.log(findDuplicate([1, 3, 4, 2, 2])); // 2 console.log(findDuplicate([3, 1, 3, 4, 2])); // 3

Basic HTML Sanitizer

Write a basic function to sanitize an HTML string, removing potentially harmful tags (like <script>) while keeping safe tags (like <p>, <b>).

Explanation: This is a complex topic, and real-world sanitizers are sophisticated. For an interview, a basic approach might involve using Regex to remove specific tags or only allowing a whitelist of tags. This is not secure for production.

function basicSanitize(html) { // WARNING: This is a very basic and insecure example. // Do NOT use this in production. Use a library like DOMPurify. // Remove script tags let sanitized = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, ''); // Example: Remove onclick attributes (very basic) sanitized = sanitized.replace(/\s(on\w+)=['"]?[^'"]*['"]?/gi, ''); return sanitized; } console.log(basicSanitize('<p>Hello <script>alert("XSS")</script> <b onclick="danger()">World</b></p>')); // <p>Hello <b >World</b></p>

Edit Distance

Given two strings word1 and word2, return the minimum number of operations (insert, delete, or substitute) required to convert word1 to word2.

Explanation: This is a classic dynamic programming problem (Levenshtein distance). Create a 2D array dp where dp[i][j] is the edit distance between the first i chars of word1 and the first j chars of word2. The recurrence relation involves considering the cost of insertion, deletion, and substitution.

function minDistance(word1, word2) { const m = word1.length, n = word2.length; const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0)); for (let i = 0; i <= m; i++) dp[i][0] = i; for (let j = 0; j <= n; j++) dp[0][j] = j; for (let i = 1; i <= m; i++) { for (let j = 1; j <= n; j++) { const cost = word1[i - 1] === word2[j - 1] ? 0 : 1; dp[i][j] = Math.min( dp[i - 1][j] + 1, // Deletion dp[i][j - 1] + 1, // Insertion dp[i - 1][j - 1] + cost // Substitution/Match ); } } return dp[m][n]; } console.log(minDistance('horse', 'ros')); // 3 console.log(minDistance('intention', 'execution')); // 5

Longest Increasing Subsequence (LIS)

Given an integer array nums, return the length of the longest strictly increasing subsequence.

Explanation: Use dynamic programming. Let dp[i] be the length of the LIS ending at index i. To calculate dp[i], find all j < i such that nums[j] < nums[i], and take dp[i] = 1 + max(dp[j]). The final answer is the maximum value in the dp array.

function lengthOfLIS(nums) { if (nums.length === 0) return 0; const n = nums.length; const dp = new Array(n).fill(1); let maxLength = 1; for (let i = 1; i < n; i++) { for (let j = 0; j < i; j++) { if (nums[i] > nums[j]) { dp[i] = Math.max(dp[i], dp[j] + 1); } } maxLength = Math.max(maxLength, dp[i]); } return maxLength; } console.log(lengthOfLIS([10, 9, 2, 5, 3, 7, 101, 18])); // 4 (2, 3, 7, 101)

N-Queens Problem

The N-Queens puzzle is the problem of placing N chess queens on an N×N chessboard so that no two queens threaten each other. Given N, return one valid solution (or all).

Explanation: Use backtracking. Place queens row by row. For each row, try placing a queen in each column. Before placing, check if the proposed position is safe (not attacked by queens in previous rows). If safe, place it and recur for the next row. If recursion fails, backtrack (remove the queen) and try the next column.

function solveNQueens(n) { const results = []; const board = Array(n).fill('.').map(() => Array(n).fill('.')); function isSafe(row, col) { for (let i = 0; i < row; i++) if (board[i][col] === 'Q') return false; // Check col for (let i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) if (board[i][j] === 'Q') return false; // Check diag up-left for (let i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) if (board[i][j] === 'Q') return false; // Check diag up-right return true; } function backtrack(row) { if (row === n) { results.push(board.map(r => r.join(''))); return; } for (let col = 0; col < n; col++) { if (isSafe(row, col)) { board[row][col] = 'Q'; backtrack(row + 1); board[row][col] = '.'; // Backtrack } } } backtrack(0); return results; } console.log(solveNQueens(4)); // [ [ '.Q..', '...Q', 'Q...', '..Q.' ], [ '..Q.', 'Q...', '...Q', '.Q..' ] ]

Use `WeakMap` for Private Data

Demonstrate how to use WeakMap to store private data for class instances.

Explanation: WeakMap allows you to associate data with an object in a way that doesn't prevent garbage collection if the object is no longer referenced. This is useful for creating 'private' members in JavaScript classes before native private fields were widely available or for specific use cases.

const privateData = new WeakMap(); class Person { constructor(name, age) { privateData.set(this, { name: name, age: age }); } getName() { return privateData.get(this).name; } getAge() { return privateData.get(this).age; } celebrateBirthday() { privateData.get(this).age++; } } const person = new Person('Alice', 30); console.log(person.getName()); // Alice person.celebrateBirthday(); console.log(person.getAge()); // 31 // console.log(person.name); // undefined - 'name' isn't a public property // console.log(privateData.get(person)); // Accessible, but not directly via 'person.'

Implement `Promise.allSettled`

Implement a function that behaves like Promise.allSettled.

Explanation: Promise.allSettled takes an array of promises and returns a single promise. This promise fulfills when all input promises have settled (either fulfilled or rejected). The fulfillment value is an array of objects, each describing the outcome of one promise ({status: 'fulfilled', value: ...} or {status: 'rejected', reason: ...}).

function myPromiseAllSettled(promises) { return new Promise((resolve) => { const numPromises = promises.length; const results = []; let settledCount = 0; if (numPromises === 0) { resolve([]); return; } promises.forEach((promise, index) => { Promise.resolve(promise) .then(value => { results[index] = { status: 'fulfilled', value: value }; }) .catch(reason => { results[index] = { status: 'rejected', reason: reason }; }) .finally(() => { settledCount++; if (settledCount === numPromises) { resolve(results); } }); }); }); } // Example: myPromiseAllSettled([Promise.resolve(1), Promise.reject('err')]) // -> [{status: 'fulfilled', value: 1}, {status: 'rejected', reason: 'err'}]

Find the Median of Two Sorted Arrays

Given two sorted arrays nums1 and nums2 of size m and n respectively, return the median of the two sorted arrays.

Explanation: This is a hard problem often solved with a binary search approach on the smaller array. The goal is to partition both arrays such that all elements on the left side are less than or equal to all elements on the right side, and the number of elements on the left equals (or is one more than) the right. The median can then be calculated from the boundary elements.

function findMedianSortedArrays(nums1, nums2) { if (nums1.length > nums2.length) [nums1, nums2] = [nums2, nums1]; // Ensure nums1 is smaller const m = nums1.length, n = nums2.length; let low = 0, high = m; while (low <= high) { const partitionX = Math.floor((low + high) / 2); const partitionY = Math.floor((m + n + 1) / 2) - partitionX; const maxX = (partitionX === 0) ? -Infinity : nums1[partitionX - 1]; const minX = (partitionX === m) ? Infinity : nums1[partitionX]; const maxY = (partitionY === 0) ? -Infinity : nums2[partitionY - 1]; const minY = (partitionY === n) ? Infinity : nums2[partitionY]; if (maxX <= minY && maxY <= minX) { if ((m + n) % 2 === 0) { return (Math.max(maxX, maxY) + Math.min(minX, minY)) / 2; } else { return Math.max(maxX, maxY); } } else if (maxX > minY) { high = partitionX - 1; } else { low = partitionX + 1; } } throw new Error('Input arrays are not sorted'); } console.log(findMedianSortedArrays([1, 3], [2])); // 2.0 console.log(findMedianSortedArrays([1, 2], [3, 4])); // 2.5

Sudoku Solver

Write a program to solve a Sudoku puzzle by filling the empty cells.

Explanation: Use backtracking. Find an empty cell. Try filling it with numbers 1 through 9. For each number, check if it's valid (doesn't violate Sudoku rules). If valid, recursively call the solver. If the recursion returns true, a solution is found. If not, backtrack (reset the cell) and try the next number.

function solveSudoku(board) { function isValid(row, col, numStr) { for (let i = 0; i < 9; i++) { if (board[row][i] === numStr) return false; // Check row if (board[i][col] === numStr) return false; // Check col const boxRow = 3 * Math.floor(row / 3) + Math.floor(i / 3); const boxCol = 3 * Math.floor(col / 3) + i % 3; if (board[boxRow][boxCol] === numStr) return false; // Check box } return true; } function backtrack() { for (let r = 0; r < 9; r++) { for (let c = 0; c < 9; c++) { if (board[r][c] === '.') { for (let n = 1; n <= 9; n++) { const numStr = String(n); if (isValid(r, c, numStr)) { board[r][c] = numStr; if (backtrack()) return true; board[r][c] = '.'; // Backtrack } } return false; // No valid number found } } } return true; // Board solved } backtrack(); return board; } // Example requires a 9x9 board with '.' for empty cells.

Implement a Basic Middleware Pattern

Implement a simple middleware pattern often seen in web frameworks.

Explanation: Middleware functions process a request before it reaches the final handler. Each middleware can modify the request/response or pass control to the next middleware. Create a class or object that manages a list of middleware functions and executes them in sequence.

class App { constructor() { this.middlewares = []; } use(fn) { this.middlewares.push(fn); } handle(request) { let index = 0; const next = () => { if (index < this.middlewares.length) { const middleware = this.middlewares[index++]; middleware(request, next); } }; next(); } } const app = new App(); app.use((req, next) => { console.log('1: Logging...'); req.logged = true; next(); }); app.use((req, next) => { console.log('2: Authenticating...'); req.authed = true; next(); }); app.use((req, next) => { console.log('3: Final Handler:', req); }); app.handle({ data: 'some request' }); // 1: Logging... // 2: Authenticating... // 3: Final Handler: { data: 'some request', logged: true, authed: true }

Detect Cycle in Directed Graph

Given a directed graph, write a function to determine if it contains a cycle.

Explanation: Use Depth First Search (DFS). Maintain two sets: visiting (nodes currently in the recursion stack) and visited (nodes that have been fully explored). If you encounter a node in the visiting set during DFS, you've found a back edge, which means there's a cycle.

function hasCycleDirected(graph) { const visiting = new Set(); const visited = new Set(); const nodes = Object.keys(graph); function dfs(node) { visiting.add(node); for (const neighbor of graph[node]) { if (visiting.has(neighbor)) return true; // Cycle detected if (!visited.has(neighbor)) { if (dfs(neighbor)) return true; } } visiting.delete(node); visited.add(node); return false; } for (const node of nodes) { if (!visited.has(node)) { if (dfs(node)) return true; } } return false; } const cyclicGraph = { A: ['B'], B: ['C'], C: ['A'] }; const acyclicGraph = { A: ['B', 'C'], B: ['D'], C: ['E'], D: [], E: [] }; console.log(hasCycleDirected(cyclicGraph)); // true console.log(hasCycleDirected(acyclicGraph)); // false

Implement `Object.freeze` behavior

Explain Object.freeze and implement a (shallow) version.

Explanation: Object.freeze prevents adding new properties, removing existing properties, and changing existing properties or their enumerability, configurability, or writability. It makes an object immutable (shallowly). You can implement it using Object.preventExtensions and Object.defineProperty to make existing properties non-writable and non-configurable.

function myFreeze(obj) { Object.preventExtensions(obj); // Prevent adding new properties Object.keys(obj).forEach(key => { Object.defineProperty(obj, key, { writable: false, configurable: false }); }); return obj; } const obj = { a: 1, b: 2 }; myFreeze(obj); // obj.c = 3; // Fails silently (or throws in strict mode) // delete obj.a; // Fails silently (or throws in strict mode) // obj.a = 10; // Fails silently (or throws in strict mode) console.log(obj); // { a: 1, b: 2 }

Use `requestAnimationFrame` for Smooth Animation

Explain requestAnimationFrame and show how to use it for a simple animation loop.

Explanation: requestAnimationFrame (rAF) tells the browser you wish to perform an animation and requests that the browser call a specified function to update an animation before the next repaint. It's more efficient and smoother than using setTimeout or setInterval for animations, as it syncs with the browser's refresh rate and pauses when the tab isn't visible.

// HTML: <div id='box' style='width: 50px; height: 50px; background: red; position: absolute; left: 0px;'></div> const box = document.getElementById('box'); let position = 0; let startTime = null; function animate(currentTime) { if (!startTime) startTime = currentTime; const elapsedTime = currentTime - startTime; // Move 100px in 2 seconds (50px/sec) position = (elapsedTime / 2000) * 100; if (position < 200) { // Keep animating until it reaches 200px box.style.left = position + 'px'; requestAnimationFrame(animate); } else { box.style.left = '200px'; } } // Start the animation // requestAnimationFrame(animate); // Uncomment to run in browser console.log('Use requestAnimationFrame for browser animations.');

Implement `Array.prototype.some`

Implement your own version of Array.prototype.some.

Explanation: some tests whether at least one element in the array passes the test implemented by the provided function. It returns true if it finds an element for which the callback returns true; otherwise, it returns false.

function mySome(arr, callback, thisArg) { for (let i = 0; i < arr.length; i++) { if (i in arr) { // Handle sparse arrays if (callback.call(thisArg, arr[i], i, arr)) { return true; } } } return false; } const arr = [1, 2, 3, 4, 5]; const hasEven = mySome(arr, x => x % 2 === 0); console.log(hasEven); // true

Implement `Array.prototype.every`

Implement your own version of Array.prototype.every.

Explanation: every tests whether all elements in the array pass the test implemented by the provided function. It returns true if all elements pass (or if the array is empty), and false if any element fails.

function myEvery(arr, callback, thisArg) { for (let i = 0; i < arr.length; i++) { if (i in arr) { if (!callback.call(thisArg, arr[i], i, arr)) { return false; } } } return true; } const arr1 = [1, 30, 39, 29, 10, 13]; const isBelow40 = myEvery(arr1, x => x < 40); console.log(isBelow40); // true const arr2 = [1, 30, 39, 29, 10, 45]; const isBelow40_2 = myEvery(arr2, x => x < 40); console.log(isBelow40_2); // false

Implement `Array.prototype.findIndex`

Implement your own version of Array.prototype.findIndex.

Explanation: findIndex returns the index of the first element in the array that satisfies the provided testing function. Otherwise, it returns -1, indicating that no element passed the test.

function myFindIndex(arr, callback, thisArg) { for (let i = 0; i < arr.length; i++) { if (i in arr) { if (callback.call(thisArg, arr[i], i, arr)) { return i; } } } return -1; } const arr = [5, 12, 8, 130, 44]; const isLargeNumber = (element) => element > 13; console.log(myFindIndex(arr, isLargeNumber)); // 3

What are Web Workers?

Explain what Web Workers are and their primary use case.

Explanation: Web Workers are a means for web content to run scripts in background threads. The main use case is to offload long-running or computationally intensive tasks from the main thread (which handles UI updates and user interactions), thus preventing the UI from becoming unresponsive or 'freezing'. Workers communicate with the main thread via message passing (postMessage and onmessage).

// main.js /* if (window.Worker) { const myWorker = new Worker('worker.js'); myWorker.postMessage([5, 3]); // Send data to worker myWorker.onmessage = function(e) { console.log('Result from worker:', e.data); } } else { console.log('Your browser does not support Web Workers.'); } */ // worker.js /* self.onmessage = function(e) { console.log('Message received from main script'); const result = e.data[0] * e.data[1]; console.log('Posting result back to main script'); self.postMessage(result); } */ console.log('Web Workers run scripts in background threads.');

Decode Ways

A message containing letters from A-Z can be encoded into numbers using the mapping 'A' -> 1, 'B' -> 2, ..., 'Z' -> 26. Given a string s containing only digits, return the number of ways to decode it.

Explanation: Use dynamic programming. Let dp[i] be the number of ways to decode s[0...i-1]. dp[i] depends on dp[i-1] (if s[i-1] is a valid 1-digit code) and dp[i-2] (if s[i-2...i-1] is a valid 2-digit code).

function numDecodings(s) { if (!s || s[0] === '0') return 0; const n = s.length; const dp = new Array(n + 1).fill(0); dp[0] = 1; dp[1] = 1; for (let i = 2; i <= n; i++) { const oneDigit = parseInt(s.substring(i - 1, i)); const twoDigits = parseInt(s.substring(i - 2, i)); if (oneDigit >= 1) { dp[i] += dp[i - 1]; } if (twoDigits >= 10 && twoDigits <= 26) { dp[i] += dp[i - 2]; } } return dp[n]; } console.log(numDecodings('12')); // 2 ('AB' or 'L') console.log(numDecodings('226')); // 3 ('BBF', 'BZ', 'VF') console.log(numDecodings('06')); // 0

Bitwise Addition (without `+`)

Write a function to add two integers without using the + operator.

Explanation: Use bitwise operators. The sum can be calculated as a ^ b (sum without carry), and the carry can be calculated as (a & b) << 1. Repeat this process until the carry becomes 0.

function getSum(a, b) { while (b !== 0) { const carry = (a & b) << 1; // Calculate carry a = a ^ b; // Calculate sum without carry b = carry; // Carry becomes the new 'b' } return a; } console.log(getSum(3, 5)); // 8 console.log(getSum(-2, 3)); // 1

Check for Palindrome (Number)

Given an integer x, return true if x is a palindrome, and false otherwise.

Explanation: A negative number is not a palindrome. Convert the number to a string and check if the string is a palindrome (reads the same forwards and backward). Alternatively, reverse the number mathematically and compare.

function isPalindromeNumber(x) { if (x < 0) return false; // String approach // return String(x) === String(x).split('').reverse().join(''); // Mathematical approach let original = x; let reversed = 0; while (x > 0) { reversed = reversed * 10 + (x % 10); x = Math.floor(x / 10); } return original === reversed; } console.log(isPalindromeNumber(121)); // true console.log(isPalindromeNumber(-121)); // false console.log(isPalindromeNumber(10)); // false

Factory Pattern

Implement the Factory design pattern in JavaScript.

Explanation: The Factory pattern provides an interface for creating objects in a superclass, but lets subclasses alter the type of objects that will be created. It's useful for creating objects without specifying the exact class.

class Car { constructor(options) { this.type = 'Car'; this.doors = options.doors || 4; } } class Truck { constructor(options) { this.type = 'Truck'; this.bedSize = options.bedSize || 'long'; } } class VehicleFactory { createVehicle(options) { if (options.vehicleType === 'car') { return new Car(options); } else if (options.vehicleType === 'truck') { return new Truck(options); } else { throw new Error('Unknown vehicle type'); } } } const factory = new VehicleFactory(); const car = factory.createVehicle({ vehicleType: 'car', doors: 2 }); const truck = factory.createVehicle({ vehicleType: 'truck', bedSize: 'short' }); console.log(car); // Car { type: 'Car', doors: 2 } console.log(truck); // Truck { type: 'Truck', bedSize: 'short' }

Explain `Symbol` and a Use Case

Explain what Symbol is in JavaScript and provide a use case, such as creating 'private' properties or unique object keys.

Explanation: Symbol is a primitive data type introduced in ES6. Its instances are unique and immutable. They are often used as keys for object properties to avoid name collisions between different libraries or to create pseudo-private properties (though not truly private, they are not accessible via typical iteration or Object.keys).

const _privateName = Symbol('name'); const _privateMethod = Symbol('greet'); class Person { constructor(name) { this[_privateName] = name; } [_privateMethod]() { console.log(`Hello, my name is ${this[_privateName]}`); } introduce() { this[_privateMethod](); } } const p = new Person('Bob'); p.introduce(); // Hello, my name is Bob console.log(Object.keys(p)); // [] - Symbols are not included console.log(p[_privateName]); // Bob (Accessible if you have the Symbol)

Convert `arguments` to a Real Array

How can you convert the arguments object (available in non-arrow functions) into a real JavaScript array?

Explanation: The arguments object looks like an array but isn't one; it lacks methods like map, filter, etc. You can convert it using:

  1. Array.from(arguments) (ES6)
  2. [...arguments] (ES6 Spread Syntax)
  3. Array.prototype.slice.call(arguments) (Older way)
function sumAll() { // 1. Array.from const args1 = Array.from(arguments); console.log('Using Array.from:', args1.reduce((a, b) => a + b, 0)); // 2. Spread Syntax const args2 = [...arguments]; console.log('Using Spread:', args2.reduce((a, b) => a + b, 0)); // 3. Slice const args3 = Array.prototype.slice.call(arguments); console.log('Using Slice:', args3.reduce((a, b) => a + b, 0)); } sumAll(1, 2, 3, 4); // Using Array.from: 10 // Using Spread: 10 // Using Slice: 10