Welcome to the definitive guide for preparing for JavaScript and TypeScript technical interviews. This resource is designed for candidates of all levels, from junior to senior, and aims to provide a deep, practical, and comprehensive overview of the concepts you’ll need to master.
This guide is structured into three main parts:
Each question is followed by a detailed explanation, one or more code solutions, and a complexity analysis where applicable.
var, let, and const?Question: Explain the key differences in scoping, hoisting, and reassignment for var, let, and const.
Answer:
var:
undefined.let:
{}).const:
Code Example:
function scopeTest() {
var a = 1;
if (true) {
var a = 2; // Re-declares and reassigns 'a'
let b = 3;
const c = 4;
console.log(b, c); // 3, 4
}
// console.log(b); // ReferenceError: b is not defined
console.log(a); // 2 (function-scoped var was overwritten)
}
scopeTest();
Question: Explain hoisting and provide code examples for variable and function hoisting.
Answer: Hoisting is JavaScript’s behavior of moving declarations to the top of their scope during compilation.
var variables are hoisted and initialized to undefined.let and const variables are hoisted but remain uninitialized, creating a Temporal Dead Zone (TDZ).Code Example:
console.log(myVar); // undefined
var myVar = 5;
// console.log(myLet); // ReferenceError: Cannot access 'myLet' before initialization
let myLet = 10;
sayHello(); // "Hello!"
function sayHello() {
console.log("Hello!");
}
// sayGoodbye(); // TypeError: sayGoodbye is not a function
var sayGoodbye = function() {
console.log("Goodbye!");
};
Question: Define what a closure is and provide a practical example.
Answer: A closure is the combination of a function and the lexical environment within which that function was declared. It gives an inner function access to an outer function’s scope, even after the outer function has returned. This is used for data privacy, stateful functions, and functional patterns.
Code Example (Data Privacy):
function createCounter() {
let count = 0; // Private variable
return {
increment: () => count++,
getValue: () => count,
};
}
const counter = createCounter();
counter.increment();
console.log(counter.getValue()); // 1
// console.log(counter.count); // undefined
setTimeout loop problem.Question: What is logged by the following code and how do you fix it?
for (var i = 1; i <= 5; i++) {
setTimeout(() => console.log(i), 1000);
}
Answer:
It logs 6 five times. The setTimeout callbacks run after the loop completes, by which time i (a single, function-scoped variable) is 6.
Solutions:
let: let is block-scoped, so each iteration gets its own i.
for (let i = 1; i <= 5; i++) {
setTimeout(() => console.log(i), 1000); // Logs 1, 2, 3, 4, 5
}
for (var i = 1; i <= 5; i++) {
(function(j) {
setTimeout(() => console.log(j), 1000);
})(i);
}
curry function.Question: Explain currying and provide a generic implementation that can transform a function f(a, b, c) into curry(f)(a)(b)(c).
Answer: Currying transforms a function with multiple arguments into a sequence of nested functions, each taking a single argument.
Implementation:
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, [...args, ...args2]);
}
}
};
}
const sum = (a, b, c) => a + b + c;
const curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3)); // 6
console.log(curriedSum(1, 2)(3)); // 6
Question: What is a Promise and what are its states?
Answer:
A Promise is an object representing the eventual completion or failure of an asynchronous operation.
pending: Initial state, neither fulfilled nor rejected.fulfilled: The operation completed successfully.rejected: The operation failed.Once settled (fulfilled or rejected), a promise is immutable.
Promise.all vs race vs allSettled vs any?Question: Describe the behavior of these four static Promise methods.
Answer:
Promise.all(promises): Fulfills when all promises fulfill. Rejects if any promise rejects.Promise.race(promises): Settles (fulfills or rejects) as soon as the first promise settles.Promise.allSettled(promises): Fulfills when all promises have settled (fulfilled or rejected). Never rejects. Returns an array of status objects.Promise.any(promises): Fulfills when the first promise fulfills. Rejects only if all promises reject.async/await and how do they work?Question: Explain async/await syntax and its relation to Promises.
Answer:
async/await is syntactic sugar for Promises, making async code look synchronous.
async function: Declares a function that implicitly returns a Promise.await operator: Pauses the async function’s execution, waiting for a Promise to settle. If fulfilled, it returns the value. If rejected, it throws the error, which can be caught with try...catch.Code Example:
const fetchData = () => new Promise(resolve => setTimeout(() => resolve("Data"), 1000));
async function getData() {
try {
console.log("Fetching...");
const data = await fetchData(); // Pauses here
console.log(data); // "Data"
} catch (e) {
console.error(e);
}
}
getData();
Promise.all from scratch.Question: Write a function that mimics the behavior of Promise.all.
Answer:
function promiseAll(promises) {
return new Promise((resolve, reject) => {
const results = [];
let completed = 0;
if (promises.length === 0) {
resolve(results);
return;
}
promises.forEach((promise, index) => {
Promise.resolve(promise) // Handle non-promise values
.then(value => {
results[index] = value;
completed++;
if (completed === promises.length) {
resolve(results);
}
})
.catch(reject); // Reject immediately on any error
});
});
}
// Example:
const p1 = Promise.resolve(3);
const p2 = 42;
const p3 = new Promise((resolve) => setTimeout(resolve, 100, 'foo'));
promiseAll([p1, p2, p3]).then(values => console.log(values)); // [3, 42, "foo"]
this Keyword and Arrow Functionsthis.Question: What are the main rules that determine the value of this?
Answer:
this is the global object (window/global) or undefined in strict mode.this is the global object or undefined in strict mode.obj.method()): this is the object (obj).call, apply, bind: this is explicitly set.new Fn()): this is the new instance being created.
Precedence: new > explicit > implicit > default.this?Question: Explain the key difference in this binding between regular and arrow functions.
Answer:
Arrow functions do not have their own this binding. They inherit this lexically from their surrounding scope. This avoids the need for that = this or .bind(this) in callbacks.
Code Example:
function Timer() {
this.seconds = 0;
setInterval(() => {
this.seconds++; // `this` is the Timer instance
console.log(this.seconds);
}, 1000);
}
const timer = new Timer();
Question: Explain how JavaScript objects inherit from other objects.
Answer:
Every object has a [[Prototype]] link to another object. When a property is accessed, the engine checks the object, then its prototype, then its prototype’s prototype, and so on, up the “prototype chain” until the property is found or the chain ends (null).
Code Example:
const animal = {
speak() { console.log(`${this.name} makes a noise.`); }
};
const dog = Object.create(animal);
dog.name = "Rex";
dog.speak(); // "Rex makes a noise."
__proto__, prototype, and Object.create()?Question: Explain __proto__, prototype and how Object.create() relates.
Answer:
function.prototype: An object property on a constructor function. It becomes the prototype of all instances created with new.object.__proto__: An instance property that points to its actual prototype. It’s the link in the chain. (Legacy; use Object.getPrototypeOf()).Object.create(proto): A method to create a new object with a specified prototype, providing a direct way to set up inheritance.Question: Describe the roles of the Call Stack, Callback Queue, and Microtask Queue.
Answer: The Event Loop allows single-threaded JS to be non-blocking.
setTimeout).Promise callbacks (.then, await).setTimeout, click events, etc.Process: The loop runs tasks from the Call Stack. When the stack is empty, it runs all tasks from the Microtask Queue, then one task from the Callback Queue.
Question: Explain event delegation and its benefits.
Answer:
Event delegation is attaching a single event listener to a parent element to manage events for all its children. It works because events “bubble up” the DOM tree. We use event.target to identify the child that triggered the event.
Benefits: Better performance (fewer listeners) and automatic handling for dynamically added elements.
Code Example:
document.getElementById('parent-list').addEventListener('click', (event) => {
if (event.target && event.target.matches('li.item')) {
console.log('List item clicked:', event.target.textContent);
}
});
Question: Explain memoization and provide an example.
Answer: Memoization is an optimization technique that caches the results of expensive function calls and returns the cached result for the same inputs, avoiding repeated computation.
Code Example (Memoized Fibonacci):
function memoize(fn) {
const cache = {};
return function(...args) {
const key = JSON.stringify(args);
if (key in cache) {
return cache[key];
}
const result = fn(...args);
cache[key] = result;
return result;
};
}
const fib = (n) => {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2); // Note: For this to work with the generic memoizer, the recursive calls should be to the memoized version.
};
const memoizedFib = memoize(function fib(n) {
if (n <= 1) return n;
return memoizedFib(n - 1) + memoizedFib(n - 2);
});
console.log(memoizedFib(40)); // 102334155
Question: Explain the difference between debouncing and throttling.
Answer:
debounce function.Question: Write a function that implements debouncing.
Answer:
function debounce(func, delay) {
let timeoutId;
return function(...args) {
const context = this;
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(context, args);
}, delay);
};
}
// Usage:
const expensiveOperation = () => console.log('Performing expensive operation...');
const debouncedOp = debounce(expensiveOperation, 500);
// window.addEventListener('resize', debouncedOp);
throttle function.Question: Write a function that implements throttling.
Answer:
function throttle(func, limit) {
let inThrottle = false;
return function(...args) {
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// Usage:
const onScroll = () => console.log('Scrolled!');
const throttledScroll = throttle(onScroll, 1000);
// window.addEventListener('scroll', throttledScroll);
interface vs type alias?Question: Explain the differences between an interface and a type alias.
Answer:
interfaces can be merged; types cannot.interface uses extends; type uses intersection (&).type can alias primitives, unions, tuples; interface cannot.
Rule of thumb: Use interface for object shapes and class contracts. Use type for unions, tuples, or complex types.Question: Explain enums and give examples of numeric and string enums.
Answer: Enums define a set of named constants.
enum Direction { Up, Down, Left, Right } // 0, 1, 2, 3
console.log(Direction[0]); // "Up"
enum LogLevel { Error = "ERROR", Info = "INFO" }
Question: Explain generics in TypeScript and the problem they solve.
Answer:
Generics create reusable, type-safe components that can work with a variety of types. They allow you to define a “type variable” instead of using a specific type or any, preserving type information.
Code Example:
function identity<T>(arg: T): T {
return arg;
}
let output = identity<string>("myString"); // Type is string
Stack class.Question: Create a Stack class that works with any data type.
Answer:
class Stack<T> {
private storage: T[] = [];
push(item: T): void { this.storage.push(item); }
pop(): T | undefined { return this.storage.pop(); }
peek(): T | undefined { return this.storage[this.storage.length - 1]; }
size(): number { return this.storage.length; }
}
Partial, Readonly, Pick, Omit.Question: Demonstrate how to use four common utility types.
Answer: Utility types transform existing types.
interface User { id: number; name: string; email: string; }
// 1. Partial<T>: Makes all properties optional.
type PartialUser = Partial<User>; // { id?: number; name?: string; ... }
// 2. Readonly<T>: Makes all properties readonly.
const user: Readonly<User> = { id: 1, name: "John", email: "a@b.com" };
// user.name = "Jane"; // Error
// 3. Pick<T, K>: Creates a type with only the specified properties.
type UserPreview = Pick<User, "name" | "email">; // { name: string; email: string; }
// 4. Omit<T, K>: Creates a type with all properties except the specified ones.
type UserAuthData = Omit<User, "id">; // { name: string; email: string; }
Question: Explain conditional types in TypeScript.
Answer:
Conditional types select a type based on a condition, using a ternary-like syntax: SomeType extends OtherType ? TrueType : FalseType;. They are fundamental to creating advanced, intelligent types.
Example (Built-in NonNullable<T>):
// If T can be null or undefined, the type is `never`, otherwise it's T.
type NonNullable<T> = T extends null | undefined ? never : T;
type ID = string | null | undefined;
type ConcreteID = NonNullable<ID>; // Type is `string`
keyof operator?Question: Explain the keyof type operator.
Answer:
The keyof operator takes an object type and produces a string or numeric literal union of its keys.
interface Point {
x: number;
y: number;
}
// P is the type "x" | "y"
type P = keyof Point;
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
unknown vs any?Question: What is the difference between unknown and any?
Answer:
Both can hold any value, but unknown is the type-safe counterpart to any.
any: Opts out of type checking. You can perform any operation on an any value without checks.unknown: A value of type unknown cannot be operated on until it has been narrowed to a more specific type using type guards (like typeof or instanceof).let val: unknown = "hello";
// val.toUpperCase(); // Error: 'val' is of type 'unknown'.
if (typeof val === 'string') {
console.log(val.toUpperCase()); // OK, val is narrowed to string
}
Question: Explain decorators and provide an example. (Note: Experimental feature).
Answer:
Decorators are special declarations (@expression) that can be attached to classes, methods, properties, or parameters to modify or observe them. They are functions that run at declaration time. Requires "experimentalDecorators": true in tsconfig.json.
Example (Method Decorator):
function LogMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`Calling "${propertyKey}" with`, args);
return originalMethod.apply(this, args);
};
}
class Person {
@LogMethod
greet(name: string) {
return `Hello, ${name}!`;
}
}
new Person().greet("World"); // Logs: Calling "greet" with ["World"]
Question: Given an array of integers nums and a target, return indices of the two numbers that add up to target.
Solution (Hash Map):
function twoSum(nums: number[], target: number): number[] {
const map = new Map<number, number>(); // Stores {number: index}
for (let i = 0; i < nums.length; i++) {
const complement = target - nums[i];
if (map.has(complement)) {
return [map.get(complement)!, i];
}
map.set(nums[i], i);
}
return [];
}
Complexity: Time: O(n), Space: O(n).
Question: Merge nums2 into nums1 in-place. nums1 has enough space at the end.
Solution (Two Pointers from End):
function merge(nums1: number[], m: number, nums2: number[], n: number): void {
let p1 = m - 1, p2 = n - 1, p = m + n - 1;
while (p2 >= 0) {
if (p1 >= 0 && nums1[p1] > nums2[p2]) {
nums1[p--] = nums1[p1--];
} else {
nums1[p--] = nums2[p2--];
}
}
}
Complexity: Time: O(m + n), Space: O(1).
Question: Find the contiguous subarray with the largest sum.
Solution (Kadane’s Algorithm):
function maxSubArray(nums: number[]): number {
let maxCurrent = nums[0];
let maxGlobal = nums[0];
for (let i = 1; i < nums.length; i++) {
maxCurrent = Math.max(nums[i], maxCurrent + nums[i]);
if (maxCurrent > maxGlobal) {
maxGlobal = maxCurrent;
}
}
return maxGlobal;
}
Complexity: Time: O(n), Space: O(1).
Question: Rotate an array to the right by k steps.
Solution (Using Reverse):
function rotate(nums: number[], k: number): void {
k %= nums.length;
const reverse = (arr: number[], start: number, end: number) => {
while (start < end) {
[arr[start], arr[end]] = [arr[end], arr[start]];
start++;
end--;
}
};
reverse(nums, 0, nums.length - 1); // Reverse the whole array
reverse(nums, 0, k - 1); // Reverse the first k elements
reverse(nums, k, nums.length - 1); // Reverse the remaining elements
}
Complexity: Time: O(n), Space: O(1).
Question: Determine if an array contains any duplicate values.
Solution (Using a Set):
function containsDuplicate(nums: number[]): boolean {
const s = new Set(nums);
return s.size !== nums.length;
}
Complexity: Time: O(n), Space: O(n).
Question: Find the maximum profit from a single buy and sell.
Solution (One Pass):
function maxProfit(prices: number[]): number {
let minPrice = Infinity;
let maxProfit = 0;
for (const price of prices) {
if (price < minPrice) {
minPrice = price;
} else if (price - minPrice > maxProfit) {
maxProfit = price - minPrice;
}
}
return maxProfit;
}
Complexity: Time: O(n), Space: O(1).
Question: Return an array where answer[i] is the product of all elements of nums except nums[i]. Do not use division.
Solution (Prefix and Postfix Products):
function productExceptSelf(nums: number[]): number[] {
const n = nums.length;
const answer = new Array(n).fill(1);
let prefix = 1;
for (let i = 0; i < n; i++) {
answer[i] = prefix;
prefix *= nums[i];
}
let postfix = 1;
for (let i = n - 1; i >= 0; i--) {
answer[i] *= postfix;
postfix *= nums[i];
}
return answer;
}
Complexity: Time: O(n), Space: O(1) (excluding result array).
Question: Find all unique triplets in the array which give the sum of zero.
Solution (Sort and Two Pointers):
function threeSum(nums: number[]): number[][] {
nums.sort((a, b) => a - b);
const result: number[][] = [];
for (let i = 0; i < nums.length - 2; i++) {
if (i > 0 && nums[i] === nums[i - 1]) continue; // Skip duplicates
let left = i + 1;
let right = nums.length - 1;
while (left < right) {
const sum = nums[i] + nums[left] + nums[right];
if (sum === 0) {
result.push([nums[i], nums[left], nums[right]]);
while (left < right && nums[left] === nums[left + 1]) left++; // Skip duplicates
while (left < right && nums[right] === nums[right - 1]) right--; // Skip duplicates
left++;
right--;
} else if (sum < 0) {
left++;
} else {
right--;
}
}
}
return result;
}
Complexity: Time: O(n^2), Space: O(1) or O(n) depending on sort implementation.
Question: Find two lines that together with the x-axis form a container, such that the container contains the most water.
Solution (Two Pointers):
function maxArea(height: number[]): number {
let max = 0;
let left = 0;
let right = height.length - 1;
while (left < right) {
const h = Math.min(height[left], height[right]);
const w = right - left;
max = Math.max(max, h * w);
if (height[left] < height[right]) {
left++;
} else {
right--;
}
}
return max;
}
Complexity: Time: O(n), Space: O(1).
Question: Write a function to flatten a deeply nested array.
Solution (Recursive):
function flatten(arr) {
let result = [];
for (const item of arr) {
if (Array.isArray(item)) {
result = result.concat(flatten(item));
} else {
result.push(item);
}
}
return result;
}
// Modern ES2019+ solution
// const flatDeep = (arr) => arr.flat(Infinity);
console.log(flatten([1, [2, [3, 4], 5], 6])); // [1, 2, 3, 4, 5, 6]
Complexity: Time: O(N) where N is total elements, Space: O(D) where D is max depth.
Question: Check if a string is a palindrome, considering only alphanumeric characters and ignoring cases.
Solution (Two Pointers):
function isPalindrome(s: string): boolean {
const cleaned = s.toLowerCase().replace(/[^a-z0-9]/g, '');
let left = 0, right = cleaned.length - 1;
while (left < right) {
if (cleaned[left] !== cleaned[right]) return false;
left++;
right--;
}
return true;
}
Complexity: Time: O(n), Space: O(n) for cleaned string.
Question: Check if string t is an anagram of string s.
Solution (Character Map):
function isAnagram(s: string, t: string): boolean {
if (s.length !== t.length) return false;
const charMap = new Map<string, number>();
for (const char of s) {
charMap.set(char, (charMap.get(char) || 0) + 1);
}
for (const char of t) {
if (!charMap.has(char) || charMap.get(char) === 0) return false;
charMap.set(char, charMap.get(char)! - 1);
}
return true;
}
Complexity: Time: O(n), Space: O(k) where k is number of unique chars.
Question: Find the length of the longest substring without repeating characters.
Solution (Sliding Window with a Map):
function lengthOfLongestSubstring(s: string): number {
let maxLength = 0;
let start = 0;
const map = new Map<string, number>(); // char -> last seen index
for (let end = 0; end < s.length; end++) {
const char = s[end];
if (map.has(char) && map.get(char)! >= start) {
start = map.get(char)! + 1;
}
map.set(char, end);
maxLength = Math.max(maxLength, end - start + 1);
}
return maxLength;
}
Complexity: Time: O(n), Space: O(k) where k is number of unique chars.
Question: Implement basic string compression, e.g., “aabcccccaaa” -> “a2b1c5a3”.
Solution (Iteration):
function compressString(str) {
if (str.length === 0) return "";
let compressed = "";
let count = 1;
for (let i = 0; i < str.length; i++) {
if (str[i] === str[i + 1]) {
count++;
} else {
compressed += str[i] + count;
count = 1;
}
}
return compressed.length < str.length ? compressed : str;
}
Complexity: Time: O(n), Space: O(n).
Question: Given a string containing just (), {}, [], determine if it’s valid.
Solution (Stack):
function isValid(s: string): boolean {
const stack: string[] = [];
const map: { [key: string]: string } = { "(": ")", "{": "}", "[": "]" };
for (const char of s) {
if (map[char]) {
stack.push(char);
} else {
const lastOpen = stack.pop();
if (map[lastOpen!] !== char) return false;
}
}
return stack.length === 0;
}
Complexity: Time: O(n), Space: O(n).
Question: Group an array of strings by anagrams.
Solution (Map with Sorted Strings):
function groupAnagrams(strs: string[]): string[][] {
const map = new Map<string, string[]>();
for (const str of strs) {
const sortedStr = str.split('').sort().join('');
if (!map.has(sortedStr)) {
map.set(sortedStr, []);
}
map.get(sortedStr)!.push(str);
}
return Array.from(map.values());
}
Complexity: Time: O(N * K log K) where N is number of strings, K is max length. Space: O(N * K).
strStr()Question: Find the first occurrence of a needle in a haystack.
Solution (Built-in or Simple Loop):
function strStr(haystack: string, needle: string): number {
if (needle.length === 0) return 0;
for (let i = 0; i <= haystack.length - needle.length; i++) {
if (haystack.substring(i, i + needle.length) === needle) {
return i;
}
}
return -1;
}
// return haystack.indexOf(needle); // The easy way
Complexity: Time: O(m * n), Space: O(1).
Question: Write a function to deeply clone an object, handling nested objects, arrays, and cycles.
Solution (Recursive with a Map for Cycles):
function deepClone(obj, map = new WeakMap()) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
// Handle cycles
if (map.has(obj)) {
return map.get(obj);
}
const clone = Array.isArray(obj) ? [] : {};
map.set(obj, clone);
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
clone[key] = deepClone(obj[key], map);
}
}
return clone;
}
Complexity: Time: O(N) where N is nodes/properties, Space: O(N).
Question: Invert an object’s keys and values.
Solution (Iteration):
function invertObject(obj) {
const inverted = {};
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
inverted[obj[key]] = key;
}
}
return inverted;
}
Complexity: Time: O(n), Space: O(n).
Question: Calculate the nth Fibonacci number efficiently.
Solution (Dynamic Programming - Tabulation):
function fib(n: number): number {
if (n <= 1) return n;
const dp = [0, 1];
for (let i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
Complexity: Time: O(n), Space: O(n). (Can be optimized to O(1) space).
Question: You can climb 1 or 2 steps at a time. How many distinct ways can you climb to the top of n stairs?
Solution (Dynamic Programming): This is a Fibonacci problem in disguise.
function climbStairs(n: number): number {
if (n <= 2) return n;
let oneStepBefore = 2;
let twoStepsBefore = 1;
let allWays = 0;
for (let i = 3; i <= n; i++) {
allWays = oneStepBefore + twoStepsBefore;
twoStepsBefore = oneStepBefore;
oneStepBefore = allWays;
}
return allWays;
}
Complexity: Time: O(n), Space: O(1).
Question: Find the fewest number of coins to make up an amount.
Solution (Dynamic Programming):
function coinChange(coins: number[], amount: number): number {
const dp = new Array(amount + 1).fill(Infinity);
dp[0] = 0;
for (let i = 1; i <= amount; i++) {
for (const coin of coins) {
if (i - coin >= 0) {
dp[i] = Math.min(dp[i], dp[i - coin] + 1);
}
}
}
return dp[amount] === Infinity ? -1 : dp[amount];
}
Complexity: Time: O(amount * coins.length), Space: O(amount).
Question: Return all possible subsets of a given set of distinct integers.
Solution (Backtracking/Recursion):
function subsets(nums: number[]): number[][] {
const result: number[][] = [];
function backtrack(index: number, currentSubset: number[]) {
result.push([...currentSubset]);
for (let i = index; i < nums.length; i++) {
currentSubset.push(nums[i]);
backtrack(i + 1, currentSubset);
currentSubset.pop();
}
}
backtrack(0, []);
return result;
}
Complexity: Time: O(N * 2^N), Space: O(N * 2^N).
Question: Determine if a string s can be segmented into a space-separated sequence of one or more dictionary words.
Solution (Dynamic Programming):
function wordBreak(s: string, wordDict: string[]): boolean {
const wordSet = new Set(wordDict);
const dp = new Array(s.length + 1).fill(false);
dp[0] = true;
for (let i = 1; i <= s.length; i++) {
for (let j = 0; j < i; j++) {
if (dp[j] && wordSet.has(s.substring(j, i))) {
dp[i] = true;
break;
}
}
}
return dp[s.length];
}
Complexity: Time: O(n^2), Space: O(n).
Question: Implement enqueue and dequeue for a Queue using only Stack instances.
Solution:
class QueueWithStacks {
constructor() {
this.inStack = [];
this.outStack = [];
}
enqueue(val) {
this.inStack.push(val);
}
dequeue() {
if (this.outStack.length === 0) {
while (this.inStack.length > 0) {
this.outStack.push(this.inStack.pop());
}
}
return this.outStack.pop();
}
peek() {
if (this.outStack.length === 0) {
while (this.inStack.length > 0) {
this.outStack.push(this.inStack.pop());
}
}
return this.outStack[this.outStack.length - 1];
}
}
Complexity: Amortized O(1) for both operations.
Question: Design a stack that supports push, pop, top, and getMin in constant time.
Solution (Two Stacks):
class MinStack {
private stack: number[] = [];
private minStack: number[] = [];
push(val: number): void {
this.stack.push(val);
const min = this.minStack.length > 0 ? this.minStack[this.minStack.length - 1] : Infinity;
this.minStack.push(Math.min(min, val));
}
pop(): void {
this.stack.pop();
this.minStack.pop();
}
top(): number {
return this.stack[this.stack.length - 1];
}
getMin(): number {
return this.minStack[this.minStack.length - 1];
}
}
Complexity: Time: O(1) for all operations, Space: O(n).
Question: Reverse a singly linked list.
Solution (Iterative):
class ListNode {
val: number;
next: ListNode | null;
constructor(val = 0, next = null) { this.val = val; this.next = next; }
}
function reverseList(head: ListNode | null): ListNode | null {
let prev = null;
let curr = head;
while (curr) {
const nextTemp = curr.next;
curr.next = prev;
prev = curr;
curr = nextTemp;
}
return prev;
}
Complexity: Time: O(n), Space: O(1).
Question: Determine if a linked list has a cycle.
Solution (Floyd’s Tortoise and Hare Algorithm):
function hasCycle(head: ListNode | null): boolean {
let slow = head, fast = head;
while (fast && fast.next) {
slow = slow!.next;
fast = fast.next.next;
if (slow === fast) return true;
}
return false;
}
Complexity: Time: O(n), Space: O(1).
Question: Merge two sorted linked lists into one sorted list.
Solution (Iterative with Dummy Node):
function mergeTwoLists(l1: ListNode | null, l2: ListNode | null): ListNode | null {
const dummy = new ListNode();
let tail = dummy;
while (l1 && l2) {
if (l1.val < l2.val) {
tail.next = l1;
l1 = l1.next;
} else {
tail.next = l2;
l2 = l2.next;
}
tail = tail.next;
}
tail.next = l1 || l2;
return dummy.next;
}
Complexity: Time: O(m + n), Space: O(1).
Question: Find the maximum depth (height) of a binary tree.
Solution (Recursive):
class TreeNode {
val: number;
left: TreeNode | null;
right: TreeNode | null;
constructor(val = 0, left = null, right = null) { this.val = val; this.left = left; this.right = right; }
}
function maxDepth(root: TreeNode | null): number {
if (!root) return 0;
return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
}
Complexity: Time: O(n), Space: O(h) where h is height.
Question: Invert a binary tree.
Solution (Recursive):
function invertTree(root: TreeNode | null): TreeNode | null {
if (!root) return null;
[root.left, root.right] = [invertTree(root.right), invertTree(root.left)];
return root;
}
Complexity: Time: O(n), Space: O(h).
Question: Determine if a binary tree is a valid Binary Search Tree (BST).
Solution (Recursive with Range):
function isValidBST(root: TreeNode | null, min = -Infinity, max = Infinity): boolean {
if (!root) return true;
if (root.val <= min || root.val >= max) return false;
return isValidBST(root.left, min, root.val) && isValidBST(root.right, root.val, max);
}
Complexity: Time: O(n), Space: O(h).
Question: Traverse a binary tree level by level.
Solution (BFS with a Queue):
function levelOrder(root: TreeNode | null): number[][] {
if (!root) return [];
const result: number[][] = [];
const queue: TreeNode[] = [root];
while (queue.length > 0) {
const levelSize = queue.length;
const currentLevel: number[] = [];
for (let i = 0; i < levelSize; i++) {
const node = queue.shift()!;
currentLevel.push(node.val);
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
}
result.push(currentLevel);
}
return result;
}
Complexity: Time: O(n), Space: O(w) where w is max width.
Question: Given a 2D grid of ‘1’s (land) and ‘0’s (water), count the number of islands.
Solution (DFS):
function numIslands(grid: string[][]): number {
if (!grid || grid.length === 0) return 0;
let count = 0;
function dfs(r: number, c: number) {
if (r < 0 || r >= grid.length || c < 0 || c >= grid[0].length || grid[r][c] === '0') {
return;
}
grid[r][c] = '0'; // Mark as visited
dfs(r + 1, c);
dfs(r - 1, c);
dfs(r, c + 1);
dfs(r, c - 1);
}
for (let r = 0; r < grid.length; r++) {
for (let c = 0; c < grid[0].length; c++) {
if (grid[r][c] === '1') {
count++;
dfs(r, c);
}
}
}
return count;
}
Complexity: Time: O(MN), Space: O(MN) in worst case for recursion stack.
Question: Write a function to perform a binary search on a sorted array.
Solution:
function search(nums: number[], target: number): number {
let left = 0, right = nums.length - 1;
while (left <= right) {
const mid = Math.floor(left + (right - left) / 2);
if (nums[mid] === target) {
return mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
Complexity: Time: O(log n), Space: O(1).
Question: Write a function to sort an array using the Quick Sort algorithm.
Solution:
function quickSort(arr) {
if (arr.length <= 1) return arr;
const pivot = arr[arr.length - 1];
const left = [];
const right = [];
for (let i = 0; i < arr.length - 1; i++) {
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return [...quickSort(left), pivot, ...quickSort(right)];
}
Complexity: Average Time: O(n log n), Worst Time: O(n^2), Space: O(log n).
Question: Write a function to sort an array using the Merge Sort algorithm.
Solution:
function mergeSort(arr) {
if (arr.length <= 1) return arr;
const mid = Math.floor(arr.length / 2);
const left = mergeSort(arr.slice(0, mid));
const right = mergeSort(arr.slice(mid));
return merge(left, right);
}
function merge(left, right) {
const result = [];
let i = 0, j = 0;
while (i < left.length && j < right.length) {
if (left[i] < right[j]) {
result.push(left[i++]);
} else {
result.push(right[j++]);
}
}
return result.concat(left.slice(i)).concat(right.slice(j));
}
Complexity: Time: O(n log n), Space: O(n).
Q66: == vs ===?
== (Loose Equality): Compares values after performing type coercion. Can lead to unexpected results (e.g., "" == false is true).=== (Strict Equality): Compares both value and type, without type coercion. Generally preferred.Q67: What is a “pure function”? A function that, given the same input, will always return the same output and has no observable side effects (e.g., modifying external state, logging to console).
Q68: What are the ways to create an object in JavaScript?
const obj = {};function MyObj() {} const obj = new MyObj();Object.create(): const obj = Object.create(prototype);class MyObj {} const obj = new MyObj();Q69: Object.freeze() vs Object.seal()?
Object.seal(obj): Prevents adding/deleting properties. Existing properties can still be changed.Object.freeze(obj): Does everything seal does, but also makes existing properties read-only. It’s a “deeper” lockdown.Q70: What is the arguments object?
An array-like object accessible inside regular functions that contains the values of the arguments passed to that function. It’s not a true array. Arrow functions do not have their own arguments object. Prefer using rest parameters (...args).
Q71: JSON.stringify vs JSON.parse?
JSON.stringify(obj): Converts a JavaScript object into a JSON string.JSON.parse(str): Converts a JSON string into a JavaScript object.Q72: Explain the Same-origin policy and CORS.
Q73: What is tree-shaking? A dead-code elimination process used by modern JavaScript bundlers (like Webpack or Rollup). It analyzes your code and dependencies and removes any unused code, resulting in smaller bundle sizes.
Q74: ?. (optional chaining) vs ?? (nullish coalescing)?
?.): Allows reading the value of a property located deep within a chain of connected objects without having to validate each reference in the chain. Returns undefined if a reference is nullish (null or undefined).
const city = user?.address?.city;??): A logical operator that returns its right-hand side operand when its left-hand side operand is null or undefined, and otherwise returns its left-hand side operand.
const displayName = user.name ?? 'Guest';Of course. Here is a comprehensive, detailed explanation for the instanceof operator, as requested.
instanceof operator?Question: Explain in detail what the instanceof operator is, how it works under the hood, and discuss its common use cases and potential pitfalls.
Answer:
The instanceof operator in JavaScript is a binary operator that tests whether the prototype property of a constructor function appears anywhere in the prototype chain of an object. It provides a way to determine an object’s type based on its constructor heritage.
The syntax is:
object instanceof Constructor
object: The object to be tested.Constructor: A constructor function (or a class).The operator returns true if the object is an instance of the constructor or an instance of a class that inherits from the constructor. Otherwise, it returns false.
When you execute object instanceof Constructor, the JavaScript engine performs the following check:
object (i.e., Object.getPrototypeOf(object)).Constructor.prototype.===), it returns true.Object.getPrototypeOf(Object.getPrototypeOf(object))) and repeats the comparison.true) or it reaches the end of the prototype chain (where the prototype is null). If the end of the chain is reached without a match, it returns false.Let’s illustrate this with a classic inheritance example.
// Base constructor function
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(`${this.name} makes a noise.`);
};
// Child constructor function inheriting from Animal
function Dog(name, breed) {
Animal.call(this, name); // Call super constructor
this.breed = breed;
}
// Set up the prototype chain: Dog.prototype -> Animal.prototype -> Object.prototype -> null
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // Reset the constructor property
const myDog = new Dog('Rex', 'German Shepherd');
// --- Let's run the checks ---
// 1. Is myDog an instance of Dog?
// - Object.getPrototypeOf(myDog) === Dog.prototype -> TRUE
console.log(myDog instanceof Dog); // true
// 2. Is myDog an instance of Animal?
// - Object.getPrototypeOf(myDog) === Animal.prototype -> FALSE
// - Object.getPrototypeOf(Object.getPrototypeOf(myDog)) === Animal.prototype -> TRUE
console.log(myDog instanceof Animal); // true
// 3. Is myDog an instance of Object?
// - The chain continues up to Object.prototype
console.log(myDog instanceof Object); // true
// 4. Is myDog an instance of Array?
// - Array.prototype is not in myDog's prototype chain.
console.log(myDog instanceof Array); // false
// An instance of the base class is not an instance of the child class.
const genericAnimal = new Animal('Generic');
console.log(genericAnimal instanceof Dog); // false
The instanceof operator works on objects. It returns false for primitive values (string, number, boolean, symbol, bigint, undefined, null).
console.log('hello' instanceof String); // false
console.log(123 instanceof Number); // false
console.log(false instanceof Boolean); // false
// However, it returns true for objects created with a constructor (wrapper objects)
const strObj = new String('hello');
console.log(strObj instanceof String); // true
A “realm” in JavaScript is a distinct global environment (e.g., a window, an iframe, or a Web Worker). Each realm has its own set of global objects and its own set of built-in type constructors (Array, Object, etc.).
If an object is created in one realm and tested against a constructor from another realm, instanceof will fail, even if the object is structurally identical. This is because their prototypes are different objects in memory.
This is the most common interview gotcha related to instanceof.
// --- Fictional example to illustrate the concept ---
// Imagine this code runs inside an iframe
// const arrayFromIframe = new Array(1, 2, 3);
// Now, in the main window's script:
// console.log(arrayFromIframe instanceof Array); // FALSE!
// Why? Because `Object.getPrototypeOf(arrayFromIframe)` points to `iframe.contentWindow.Array.prototype`,
// which is a different object than the main window's `window.Array.prototype`.
// This is why `Array.isArray()` was created. It reliably checks if something is an array,
// regardless of its realm of origin.
// Array.isArray(arrayFromIframe); // TRUE
Symbol.hasInstanceES6 introduced the Symbol.hasInstance well-known symbol. A class or constructor function can define a static method with this symbol as its key to completely override the default behavior of instanceof.
class SpecialCheck {
static [Symbol.hasInstance](instance) {
// Custom logic: check if the instance has a specific property
if (instance && instance.isSpecial) {
return true;
}
return false;
}
}
const obj1 = { isSpecial: true };
const obj2 = { isSpecial: false };
const obj3 = {};
console.log(obj1 instanceof SpecialCheck); // true
console.log(obj2 instanceof SpecialCheck); // false
console.log(obj3 instanceof SpecialCheck); // false
instanceof is excellent for checking the type of custom objects created via your own classes or constructor functions within a single realm.instanceof for built-in types like Array, especially in complex applications with iframes or multiple windows. Prefer modern, purpose-built methods like Array.isArray().prototype property of the constructor against the object’s prototype chain. It does not check the object’s structure or “duck type.”Question: What are Web Workers and how do you use them? Provide a code example.
Answer:
Web Workers are a simple means for web content to run scripts in a background thread. The worker thread can perform tasks without interfering with the user interface, preventing the main page from becoming unresponsive. The main script and the worker communicate using a system of messages—both sides send messages with postMessage() and respond to messages via the onmessage event handler. Workers cannot directly access the DOM.
Code Example: This example demonstrates a worker performing a heavy calculation (finding prime numbers) without freezing the UI.
index.html (Main Page)
<!DOCTYPE html>
<html>
<head>
<title>Web Worker Demo</title>
</head>
<body>
<h1>Web Worker Demo</h1>
<p>Click the button to calculate primes up to 10,000,000 without freezing the page.</p>
<button id="start-worker">Start Calculation</button>
<button id="update-counter">Click Me to Prove UI is Not Frozen</button>
<p>Counter: <span id="counter">0</span></p>
<h2>Result from Worker:</h2>
<div id="result"></div>
<script src="main.js"></script>
</body>
</html>
main.js (Main Script)
const startButton = document.getElementById('start-worker');
const resultDiv = document.getElementById('result');
const counterButton = document.getElementById('update-counter');
const counterSpan = document.getElementById('counter');
let count = 0;
if (window.Worker) {
const myWorker = new Worker('worker.js');
startButton.onclick = function() {
resultDiv.textContent = 'Calculating...';
// Send a message to the worker to start the task
myWorker.postMessage(10000000);
};
// Listen for messages from the worker
myWorker.onmessage = function(e) {
resultDiv.textContent = `Found ${e.data} primes.`;
console.log('Message received from worker');
};
myWorker.onerror = function(e) {
console.error('Error in worker:', e);
};
} else {
console.log('Your browser doesn\'t support Web Workers.');
}
// This button proves the UI is still responsive
counterButton.onclick = function() {
count++;
counterSpan.textContent = count;
};
worker.js (Worker Script)
// Listen for messages from the main script
self.onmessage = function(e) {
console.log('Message received in worker. Starting calculation...');
const upperLimit = e.data;
// Heavy calculation
function isPrime(num) {
for (let i = 2, s = Math.sqrt(num); i <= s; i++)
if (num % i === 0) return false;
return num > 1;
}
let primeCount = 0;
for (let i = 0; i < upperLimit; i++) {
if (isPrime(i)) {
primeCount++;
}
}
// Send the result back to the main script
self.postMessage(primeCount);
};
Question: What is a polyfill? Provide an example of a polyfill for a modern JavaScript feature.
Answer: A polyfill is a piece of code (usually JavaScript on the Web) used to provide modern functionality on older browsers that do not natively support it. It “fills in” the gap, allowing developers to use modern APIs without worrying about browser compatibility.
Code Example (Polyfill for Array.prototype.flat)
The .flat() method creates a new array with all sub-array elements concatenated into it recursively up to the specified depth.
// The polyfill checks if the method exists before defining it.
if (!Array.prototype.flat) {
Object.defineProperty(Array.prototype, 'flat', {
configurable: true,
writable: true,
value: function(depth = 1) {
// Ensure the context is an object, as per spec.
if (this == null) {
throw new TypeError("Array.prototype.flat called on null or undefined");
}
const stack = [...this];
const result = [];
while (stack.length > 0) {
const item = stack.shift();
if (Array.isArray(item) && depth > 0) {
// Push the flattened items to the front of the stack
stack.unshift(...item);
// Only decrement depth for the whole array level
// This implementation simplifies depth handling by re-flattening
// A more accurate polyfill would track depth per element.
} else {
result.push(item);
}
}
// A simplified polyfill, a full one would handle depth more precisely.
// For depth > 1, you'd recursively call or adjust the loop.
// This basic version effectively does a depth of 1.
return result;
}
});
}
// --- Usage Example ---
const nestedArray = [1, 2, [3, 4, [5, 6]]];
// Using the polyfilled method
const flattenedOnce = nestedArray.flat();
console.log(flattenedOnce); // Output: [1, 2, 3, 4, [5, 6]]
// Note: A true polyfill for depth > 1 is more complex. For example:
// const flattenedTwice = nestedArray.flat(2);
// console.log(flattenedTwice); // would be [1, 2, 3, 4, 5, 6]
Question: What are mixins in TypeScript? Provide a code example of how to implement them.
Answer: Mixins are a pattern for class composition. Instead of inheriting from a single superclass, a class can be composed of multiple smaller, reusable pieces of functionality called mixins. TypeScript implements this by having a class implement multiple interfaces and then using a helper function to merge the prototypes of the mixin classes into the target class.
Code Example:
// Mixin 1: Adds a timestamp property
class Timestamped {
timestamp: Date = new Date();
}
// Mixin 2: Adds an active property and toggle method
class Activatable {
isActive: boolean = false;
activate() { this.isActive = true; }
deactivate() { this.isActive = false; }
}
// Base Class
class User {
name: string;
constructor(name: string) {
this.name = name;
}
}
// Create a new class that will be composed of the User class and the mixins.
// It must implement the interfaces of all parts to satisfy the type checker.
interface SmartUser extends User, Timestamped, Activatable {}
class SmartUser {
constructor(name: string) {
// The base class constructor is implicitly called by the runtime.
// We can call it explicitly if needed.
// super(name); // not needed here as User constructor is simple.
this.name = name;
}
}
// Helper function to apply mixins to a class
function applyMixins(derivedCtor: any, baseCtors: any[]) {
baseCtors.forEach(baseCtor => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
Object.defineProperty(derivedCtor.prototype, name, Object.getOwnPropertyDescriptor(baseCtor.prototype, name)!);
});
});
}
// Apply the mixins to our SmartUser class
applyMixins(SmartUser, [Timestamped, Activatable]);
// --- Usage ---
const smartUser = new SmartUser("Alice");
console.log(smartUser.name); // "Alice"
// From Timestamped mixin
console.log(smartUser.timestamp); // Logs the creation date/time
// From Activatable mixin
console.log(smartUser.isActive); // false
smartUser.activate();
console.log(smartUser.isActive); // true
bind methodQuestion: Explain what Function.prototype.bind does and provide a polyfill for it.
Answer:
The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called. It essentially “locks” the this context of a function.
Code Example (Polyfill for bind)
// --- Problem demonstration ---
const module = {
x: 42,
getX: function() {
return this.x;
}
};
const unboundGetX = module.getX;
// console.log(unboundGetX()); // Throws TypeError in strict mode, or returns `undefined` (window.x)
const boundGetX = unboundGetX.bind(module);
console.log(boundGetX()); // 42
// --- Polyfill for bind ---
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
// Capture the arguments passed to bind (after the 'this' context)
const aArgs = Array.prototype.slice.call(arguments, 1);
const fToBind = this; // The original function
const fNOP = function() {}; // A no-op constructor
const fBound = function() {
// The `this` for the new function call, and its arguments
const context = this instanceof fNOP ? this : oThis;
const finalArgs = aArgs.concat(Array.prototype.slice.call(arguments));
return fToBind.apply(context, finalArgs);
};
// Maintain the prototype chain
if (this.prototype) {
fNOP.prototype = this.prototype;
}
fBound.prototype = new fNOP();
return fBound;
};
}
Question: Explain event bubbling and capturing, and show how to use them with addEventListener.
Answer: When an event occurs on an element, it goes through two phases:
You control which phase to listen for with the third argument of addEventListener(event, handler, useCapture).
useCapture = false (default): Listen during the bubbling phase.useCapture = true: Listen during the capturing phase.Code Example:
<div id="grandparent">
<div id="parent">
<div id="child">Click Me</div>
</div>
</div>
<script>
const gp = document.getElementById('grandparent');
const p = document.getElementById('parent');
const c = document.getElementById('child');
// Add listeners for the CAPTURING phase (true)
gp.addEventListener('click', () => console.log('Grandparent Capture'), true);
p.addEventListener('click', () => console.log('Parent Capture'), true);
c.addEventListener('click', () => console.log('Child Capture'), true);
// Add listeners for the BUBBLING phase (false or default)
gp.addEventListener('click', () => console.log('Grandparent Bubble'), false);
p.addEventListener('click', () => console.log('Parent Bubble'), false);
c.addEventListener('click', () => console.log('Child Bubble'), false);
/*
When you click the "child" div, the console output will be:
Grandparent Capture // Event travels down
Parent Capture // Event travels down
Child Capture // Event reaches target (capture phase listener)
Child Bubble // Event at target (bubble phase listener)
Parent Bubble // Event travels up
Grandparent Bubble // Event travels up
*/
</script>
Map, Set, WeakMap, WeakSet, SymbolThese are modern JS features. The code examples demonstrate their core functionality.
Map and Set Objects (Q81)
Map: A collection of keyed data items, just like an Object. But Map allows keys of any type.Set: A collection of unique values.// --- Set Example ---
const mySet = new Set();
mySet.add(1); // Set(1) { 1 }
mySet.add(5); // Set(2) { 1, 5 }
mySet.add(5); // Still Set(2) { 1, 5 } because values must be unique
mySet.add("some text"); // Set(3) { 1, 5, 'some text' }
console.log(mySet.has(1)); // true
// --- Map Example ---
const myMap = new Map();
const keyString = 'a string';
const keyObj = {};
const keyFunc = function() {};
myMap.set(keyString, "value associated with 'a string'");
myMap.set(keyObj, 'value associated with keyObj');
myMap.set(keyFunc, 'value associated with keyFunc');
console.log(myMap.size); // 3
console.log(myMap.get(keyObj)); // "value associated with keyObj"
WeakMap and WeakSet (Q82)
Their keys/values must be objects, and they hold “weak” references. This means if an object stored in a WeakMap/WeakSet is the only remaining reference to that object, the garbage collector is free to destroy it. They are not iterable.
// --- WeakMap Example ---
let john = { name: "John" };
const weakMap = new WeakMap();
weakMap.set(john, "secret data");
// Now, if we remove the only strong reference to `john`...
john = null;
// ...the garbage collector can remove the `john` object from memory,
// and the entry in the `weakMap` will also be removed automatically.
// This prevents memory leaks.
// We can't iterate or check the size, so we can't directly observe its removal.
Symbol Primitive Type (Q83)
A Symbol is a unique and immutable primitive value and may be used as the key of an Object property. They are often used to create “private” properties on objects that won’t conflict with string keys.
const idSymbol = Symbol('id'); // The description 'id' is for debugging only.
const user = {
name: 'Alex',
[idSymbol]: 'xyz-123' // Using a symbol as a property key
};
console.log(user.name); // 'Alex'
console.log(user[idSymbol]); // 'xyz-123'
// Symbols are not enumerated in for...in loops
for (let key in user) {
console.log(key); // Only 'name' is logged
}
// They are also ignored by JSON.stringify
console.log(JSON.stringify(user)); // '{"name":"Alex"}'
// To get symbols, you must use Object.getOwnPropertySymbols
console.log(Object.getOwnPropertySymbols(user)); // [Symbol(id)]
Question: What are generator functions? Provide an example.
Answer:
A generator function (function*) is a special function that can be paused and resumed, allowing it to produce a sequence of values over time. When called, it doesn’t run its code but returns a special Generator object. You use the yield keyword to pause the function and produce a value. The next() method on the generator object resumes execution.
Code Example:
// A generator function to create a sequence of IDs
function* idGenerator() {
let id = 1;
while (true) {
yield id++;
}
}
const gen = idGenerator(); // Get the generator object
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3
// Generators are iterable, so you can use them in for...of loops
function* fibonacci() {
let [prev, curr] = [0, 1];
while (true) {
yield prev;
[prev, curr] = [curr, prev + curr];
}
}
const fibGen = fibonacci();
for (let i = 0; i < 10; i++) {
console.log(fibGen.next().value); // Logs the first 10 Fibonacci numbers
}
Proxy and Reflect APIQuestion: What are Proxy and Reflect? Provide an example of them working together.
Answer:
Proxy: An object that wraps another object (the “target”) and allows you to intercept and redefine fundamental operations on it (like getting/setting properties, function calls, etc.). These interceptions are called “traps”.Reflect: A built-in object that provides methods for the same fundamental operations that can be trapped by Proxy. The methods on Reflect have the same names as the proxy traps. Reflect is useful for forwarding the original operation to the target from within a trap.Code Example (Validation and Logging Proxy):
const user = {
name: "John",
age: 30,
email: "john@example.com"
};
const handler = {
get(target, property) {
console.log(`Getting property "${property}"`);
// Use Reflect to perform the default get operation
return Reflect.get(target, property);
},
set(target, property, value) {
console.log(`Setting property "${property}" to "${value}"`);
if (property === 'age' && (typeof value !== 'number' || value <= 0)) {
throw new TypeError("Age must be a positive number.");
}
if (property === 'email' && !value.includes('@')) {
throw new TypeError("Invalid email format.");
}
// Use Reflect to perform the default set operation
return Reflect.set(target, property, value);
}
};
const userProxy = new Proxy(user, handler);
// --- Usage ---
console.log(userProxy.name); // Logs: Getting property "name" -> John
userProxy.age = 31; // Logs: Setting property "age" to "31"
console.log(userProxy.age); // Logs: Getting property "age" -> 31
try {
userProxy.age = -5; // Throws TypeError
} catch (e) {
console.error(e.message);
}
try {
userProxy.email = "invalid-email"; // Throws TypeError
} catch (e) {
console.error(e.message);
}
Q87: Differences between DOM and Shadow DOM
document. CSS styles from the main page apply to everything.// In an HTML file with <div id="host"></div>
const host = document.getElementById('host');
const shadowRoot = host.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `
<style> p { color: blue; } </style>
<p>This text is in the Shadow DOM and will be blue.</p>
`;
// A <p> tag in the main document will not be blue.
Q92: What is JSX?
JSX (JavaScript XML) is a syntax extension for JavaScript, popularized by React. It lets you write HTML-like markup inside a JavaScript file. This code is then transpiled by tools like Babel into standard React.createElement() calls.
// This is JSX
const element = <h1 className="greeting">Hello, world!</h1>;
// This is what it transpiles to
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
Q94: Higher-Order Components (HOCs) in React A Higher-Order Component is an advanced React pattern. It’s a function that takes a component as an argument and returns a new component, usually with some enhanced functionality or additional props.
import React, { useState, useEffect } from 'react';
// This is the HOC
function withLoading(WrappedComponent) {
// It returns a new component...
return function ComponentWithLoading(props) {
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
// Simulate a data fetch
setTimeout(() => setIsLoading(false), 2000);
}, []);
if (isLoading) {
return <div>Loading...</div>;
}
// Pass original props through to the wrapped component
return <WrappedComponent {...props} />;
};
}
// A simple component to be wrapped
function UserProfile({ name }) {
return <h1>{name}'s Profile</h1>;
}
// Create the enhanced component using the HOC
const UserProfileWithLoading = withLoading(UserProfile);
// Usage in an app
// <UserProfileWithLoading name="Sarah" />
// This will show "Loading..." for 2 seconds, then show "Sarah's Profile".
Q96: let in loops vs var
This revisits Q4. let creates a new binding for each loop iteration, whereas var declares a single variable for the entire function scope.
// `var` - all callbacks reference the SAME `i`
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(`var: ${i}`), 10); // Logs "var: 3" three times
}
// `let` - each iteration gets its OWN `i`
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(`let: ${i}`), 10); // Logs "let: 0", "let: 1", "let: 2"
}
Q97: What is a TypedArray?
A TypedArray is an array-like object for reading and writing raw binary data. They are not regular arrays, but a family of objects (like Int8Array, Float32Array, etc.) that provide a typed view over an ArrayBuffer. They are highly efficient for working with binary data, such as in WebGL, file manipulation, or networking.
// Create an ArrayBuffer of 8 bytes
const buffer = new ArrayBuffer(8);
// Create a view on the buffer that treats the data as 32-bit signed integers
// 8 bytes / 4 bytes per integer = 2 integers
const int32View = new Int32Array(buffer);
int32View[0] = 42;
int32View[1] = 100;
console.log(int32View); // Int32Array(2) [42, 100]
// Create another view on the SAME buffer, treating it as 8-bit integers
const int8View = new Int8Array(buffer);
console.log(int8View); // Int8Array(8) [42, 0, 0, 0, 100, 0, 0, 0] (due to little-endian representation)
Q98: Explain Array.prototype.reduce
The reduce() method executes a user-supplied “reducer” callback function on each element of the array, in order, passing in the return value from the calculation on the preceding element. The final result of running the reducer across all elements of the array is a single value.
// --- Example 1: Summing numbers ---
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((accumulator, currentValue) => {
return accumulator + currentValue;
}, 0); // 0 is the initial value for the accumulator
console.log(sum); // 15
// --- Example 2: Grouping objects by a property ---
const people = [
{ name: 'Alice', role: 'dev' },
{ name: 'Bob', role: 'dev' },
{ name: 'Charlie', role: 'qa' }
];
const groupedByRole = people.reduce((acc, person) => {
const key = person.role;
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(person);
return acc;
}, {}); // Initial value is an empty object
/*
groupedByRole is now:
{
dev: [ { name: 'Alice', role: 'dev' }, { name: 'Bob', role: 'dev' } ],
qa: [ { name: 'Charlie', role: 'qa' } ]
}
*/
console.log(groupedByRole);
Q99: What is dependency injection? Dependency Injection (DI) is a design pattern where a class receives its dependencies from an external source rather than creating them itself. This promotes loose coupling and makes code more modular, testable, and maintainable.
// --- Without DI ---
class BadUserService {
private logger;
constructor() {
this.logger = new ConsoleLogger(); // Tight coupling, hard to test
}
createUser() {
this.logger.log("User created");
}
}
// --- With DI ---
interface ILogger {
log(message: string): void;
}
class ConsoleLogger implements ILogger {
log(message: string) { console.log(message); }
}
class FileLogger implements ILogger {
log(message: string) { /* logic to write to a file */ }
}
// The class receives its dependency via the constructor
class GoodUserService {
constructor(private logger: ILogger) {} // Dependency is "injected"
createUser(name: string) {
this.logger.log(`User "${name}" created.`);
}
}
// The "injector" or "container" is responsible for creating dependencies
const consoleLogger = new ConsoleLogger();
const fileLogger = new FileLogger();
const service1 = new GoodUserService(consoleLogger); // Injecting ConsoleLogger
service1.createUser("Alice");
const service2 = new GoodUserService(fileLogger); // Injecting FileLogger
service2.createUser("Bob");
Q100: How do you handle errors in Promise.all?
Promise.all is “fail-fast”—it rejects as soon as one of the input promises rejects. To handle this and still get results from the successful promises, you should use Promise.allSettled. If you must use Promise.all, you can map each promise to a new promise that always resolves.
const p1 = Promise.resolve('Success 1');
const p2 = Promise.reject('Failure!');
const p3 = Promise.resolve('Success 3');
// --- Method 1: Promise.allSettled (Preferred) ---
Promise.allSettled([p1, p2, p3])
.then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log(`Fulfilled: ${result.value}`);
} else {
console.error(`Rejected: ${result.reason}`);
}
});
});
// --- Method 2: Manually handling errors for Promise.all ---
Promise.all([
p1.catch(error => ({ status: 'rejected', reason: error })),
p2.catch(error => ({ status: 'rejected', reason: error })),
p3.catch(error => ({ status: 'rejected', reason: error })),
]).then(results => {
console.log('Manual Handling Results:', results);
});
Q101: What are assertion functions in TypeScript?
An assertion function is a function that checks for a condition and throws an error if the condition is not met. Its special asserts return type signature tells the TypeScript compiler to narrow the type of a variable for the rest of the scope, just as if an if statement or typeof check had been performed.
// The assertion function with the 'asserts' keyword
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== "string") {
throw new Error("Not a string!");
}
}
function processValue(input: string | number) {
// Here, 'input' is 'string | number'
assertIsString(input);
// After the assertion, TypeScript knows 'input' MUST be a string.
// So, this is now safe:
console.log(input.toUpperCase());
}
processValue("hello"); // HELLO
// processValue(123); // Throws Error: "Not a string!"
Q102: Explain mapped types in TypeScript
A mapped type is a generic type that creates a new object type by iterating over the keys of another type (keyof T) and transforming the properties. Common utility types like Partial<T>, Readonly<T>, and Pick<T, K> are implemented using mapped types.
// --- Basic Readonly implementation ---
type MyReadonly<T> = {
readonly [P in keyof T]: T[P];
};
interface User {
name: string;
age: number;
}
const user: MyReadonly<User> = { name: "Alice", age: 30 };
// user.age = 31; // Error: Cannot assign to 'age' because it is a read-only property.
// --- Example: Creating a type with boolean flags for each property ---
type PropertyFlags<T> = {
[P in keyof T]: boolean;
};
type UserFlags = PropertyFlags<User>;
// UserFlags is equivalent to: { name: boolean; age: boolean; }
const flags: UserFlags = { name: true, age: false };
Q103: How can you create an object with a null prototype?
You can create an object with a null prototype using Object.create(null). Such an object does not inherit any properties or methods from Object.prototype, such as hasOwnProperty, toString, or constructor.
Benefits:
hasOwnProperty.const pureMap = Object.create(null);
pureMap.name = "John";
pureMap.toString = "A custom string"; // No conflict with Object.prototype.toString
console.log(pureMap.name); // "John"
console.log(pureMap.toString); // "A custom string"
// The following would throw errors because the methods don't exist:
// pureMap.hasOwnProperty('name'); // TypeError
// pureMap.constructor; // undefined
// In contrast, a regular object has inherited properties:
const regularObj = {};
console.log(regularObj.toString()); // "[object Object]"
Q104: What is the this problem in class methods in JS?
JavaScript class methods are not automatically bound to the class instance. When a method is passed as a callback (e.g., to an event listener or setTimeout), its this context is lost. In strict mode (default for classes), this becomes undefined inside the callback, leading to a TypeError when you try to access instance properties like this.name.
Code Example Demonstrating the Problem and Solutions:
class Greeter {
constructor(public message: string) {
// Solution 1: Bind 'this' in the constructor (common in older React classes)
// this.greet = this.greet.bind(this);
}
// This method has the 'this' problem
greet() {
console.log(this.message);
}
// Solution 2: Use an arrow function as a class field (modern approach)
greetWithArrow = () => {
console.log(this.message);
}
}
const greeter = new Greeter("Hello, world!");
// --- The Problem ---
// `setTimeout`'s context is the global object (or `undefined` in strict mode)
// setTimeout(greeter.greet, 500); // Throws: TypeError: Cannot read properties of undefined (reading 'message')
// --- The Solutions ---
// 1. (If uncommented in constructor):
// setTimeout(greeter.greet, 500); // Works: "Hello, world!"
// 2. Use the arrow function version
setTimeout(greeter.greetWithArrow, 1000); // Works: "Hello, world!"
// 3. Bind at the call site
setTimeout(greeter.greet.bind(greeter), 1500); // Works: "Hello, world!"