Skip to the content.
🏠 Home

← Back to Home

πŸ’» JS & React Coding Interview Questions β€” Famous, Tricky & Must-Know

Every question includes code, detailed comment-based explanations, and the exact console output.


PART 1: JavaScript Output-Based Questions

These questions test your understanding of how the JS engine actually works β€” hoisting, closures, event loop, type coercion, prototypes, and this.


πŸ”₯ 1. Hoisting β€” var vs let vs const

Q1.1: Variable Hoisting

console.log(a); // ? 
console.log(b); // ?
console.log(c); // ?

var a = 10;
let b = 20;
const c = 30;

// ─── OUTPUT ───
// undefined          ← var is hoisted and initialized as undefined
// ReferenceError     ← let is hoisted but stays in "Temporal Dead Zone" (TDZ) until declaration
// (never reaches c)  ← const behaves the same as let (TDZ)

// ─── WHY? ───
// JavaScript "hoists" all declarations to the top of their scope BEFORE execution.
// var a;        ← declaration is hoisted, value is undefined
// let b;        ← declaration is hoisted BUT accessing it before the let line = ReferenceError
// const c;      ← same as let

Q1.2: Function Hoisting

greet();       // ?
hello();       // ?

function greet() {
  console.log("Hi!");
}

var hello = function() {
  console.log("Hello!");
};

// ─── OUTPUT ───
// "Hi!"              ← Function declarations are hoisted ENTIRELY (name + body)
// TypeError: hello is not a function  
//                    ← var hello is hoisted as undefined, so undefined() throws TypeError

// ─── KEY RULE ───
// Function DECLARATIONS β†’ fully hoisted (can be called before definition)
// Function EXPRESSIONS  β†’ only the variable is hoisted (as undefined)
// Arrow functions       β†’ same as expressions (not hoisted as functions)

Q1.3: Hoisting in Block Scope

var x = 1;

if (true) {
  console.log(x); // ?
  let x = 2;
}

// ─── OUTPUT ───
// ReferenceError: Cannot access 'x' before initialization
//
// ─── WHY? ───
// Even though var x = 1 exists outside, the `let x` inside the block creates
// a NEW binding for that block. The let is hoisted to the top of the block
// but stays in the TDZ β†’ accessing it before `let x = 2` throws ReferenceError.
// This is called "shadowing" β€” the inner x shadows the outer x.

πŸ”₯ 2. Closures β€” The Most Tested Topic

Q2.1: The Classic var in Loop

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}

// ─── OUTPUT ───
// 3
// 3
// 3

// ─── WHY? ───
// `var` is FUNCTION-scoped, NOT block-scoped.
// There is only ONE variable `i` shared across all iterations.
// setTimeout callbacks go into the callback queue and execute AFTER the loop finishes.
// By then, i = 3 (the loop exit condition).
// All 3 callbacks read the SAME `i`, which is now 3.

Q2.2: Fix with let

for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}

// ─── OUTPUT ───
// 0
// 1
// 2

// ─── WHY? ───
// `let` is BLOCK-scoped. Each iteration creates a NEW `i` binding.
// Each setTimeout callback captures its own copy of `i`.

Q2.3: Fix with IIFE (Pre-ES6 approach)

for (var i = 0; i < 3; i++) {
  (function(idx) {
    // IIFE creates a new scope for each iteration
    // `idx` is a LOCAL copy of `i` at this point in time
    setTimeout(() => console.log(idx), 100);
  })(i); // pass current value of i as argument
}

// ─── OUTPUT ───
// 0
// 1
// 2

// ─── WHY? ───
// The IIFE (Immediately Invoked Function Expression) creates a new function scope
// for each iteration. The parameter `idx` captures the VALUE of `i` at that moment.
// Each setTimeout callback has its own `idx` in its closure.

Q2.4: Closure Counter

function createCounter() {
  let count = 0; // Private variable β€” only accessible through returned functions
  
  return {
    increment: () => ++count,
    decrement: () => --count,
    getCount: () => count
  };
}

const counter1 = createCounter();
const counter2 = createCounter(); // Completely separate closure

console.log(counter1.increment()); // 1
console.log(counter1.increment()); // 2
console.log(counter2.increment()); // 1  ← separate closure, separate count!
console.log(counter1.getCount());  // 2  ← counter1 is unaffected by counter2

// ─── KEY INSIGHT ───
// Each call to createCounter() creates a NEW execution context with its own `count`.
// The returned object's methods form a closure over that specific `count`.
// This is the MODULE PATTERN β€” creating private state in JavaScript.

Q2.5: Tricky Closure β€” What’s logged?

function outer() {
  var x = 10;

  function inner() {
    console.log(x); // ?
  }

  x = 20; // Reassign BEFORE inner is called

  return inner;
}

outer()(); 

// ─── OUTPUT ───
// 20

// ─── WHY? ───
// Closures capture the REFERENCE to the variable, not the VALUE at creation time.
// When inner() finally executes, it looks up x in its closure and finds 20.
// x was reassigned to 20 before inner was called.

πŸ”₯ 3. Event Loop β€” setTimeout vs Promise vs async/await

Q3.1: The Classic Order Question

console.log('1');                              // Sync

setTimeout(() => console.log('2'), 0);         // Macrotask (callback queue)

Promise.resolve().then(() => console.log('3')); // Microtask (microtask queue)

console.log('4');                              // Sync

// ─── OUTPUT ───
// 1
// 4
// 3
// 2

// ─── EXECUTION ORDER ───
// Step 1: Execute all synchronous code β†’ logs 1, then 4
// Step 2: Drain the Microtask queue β†’ Promise.then β†’ logs 3
// Step 3: Pick next Macrotask β†’ setTimeout β†’ logs 2
//
// PRIORITY: Sync Code > Microtasks (Promises) > Macrotasks (setTimeout)

Q3.2: Nested Promises and setTimeout

console.log('start');

setTimeout(() => {
  console.log('timeout 1');
  Promise.resolve().then(() => console.log('promise inside timeout'));
}, 0);

Promise.resolve().then(() => {
  console.log('promise 1');
  setTimeout(() => console.log('timeout inside promise'), 0);
});

Promise.resolve().then(() => console.log('promise 2'));

console.log('end');

// ─── OUTPUT ───
// start
// end
// promise 1
// promise 2
// timeout 1
// promise inside timeout
// timeout inside promise

// ─── STEP BY STEP ───
// 1. Sync: "start", "end"
// 2. Microtasks: "promise 1" (this also queues a new setTimeout),
//                "promise 2"
// 3. Macrotask #1: "timeout 1" (this also queues a new microtask)
// 4. Microtask (from step 3): "promise inside timeout"
// 5. Macrotask #2: "timeout inside promise" (queued in step 2)

Q3.3: async/await Execution Order

async function foo() {
  console.log('foo start');       // Sync β€” runs immediately
  
  await bar();                    // Pause foo here; everything after goes to microtask queue
  
  console.log('foo end');         // Microtask β€” resumes after bar() resolves
}

async function bar() {
  console.log('bar');             // Sync β€” runs immediately when bar() is called
}

console.log('script start');
foo();
console.log('script end');

// ─── OUTPUT ───
// script start
// foo start
// bar
// script end
// foo end

// ─── WHY? ───
// "script start" β†’ sync
// foo() is called β†’ "foo start" (sync part of foo)
// await bar() β†’ bar() runs sync ("bar"), then foo pauses
// Execution returns to the main script β†’ "script end"
// Microtask queue: resume foo after await β†’ "foo end"

Q3.4: Complex async/await + Promise + setTimeout

async function async1() {
  console.log('async1 start');
  await async2();
  console.log('async1 end');
}

async function async2() {
  console.log('async2');
}

console.log('script start');

setTimeout(() => console.log('setTimeout'), 0);

async1();

new Promise((resolve) => {
  console.log('promise1');  // Executor runs SYNCHRONOUSLY
  resolve();
}).then(() => {
  console.log('promise2');  // Microtask
});

console.log('script end');

// ─── OUTPUT ───
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout

// ─── BREAKDOWN ───
// Sync phase: script start β†’ async1 start β†’ async2 β†’ promise1 β†’ script end
// Microtask phase: async1 end (resume after await) β†’ promise2 (then callback)
// Macrotask phase: setTimeout

Q3.5: Async IIFE with await null, queueMicrotask, and Delayed Timers

console.log("A");

setTimeout(() => {
  console.log("B");
}, 2000);

Promise.resolve().then(() => {
  console.log("C");
});

queueMicrotask(() => {
  console.log("D");
});

(async () => {
  console.log("E");
  await null; // wraps in Promise.resolve(null) & pauses IIFE
  console.log("F");
})();

setTimeout(() => {
  console.log("G");
  Promise.resolve().then(() => {
    console.log("H");
  });
}, 2000);

// ─── OUTPUT ───
// A
// E
// C
// D
// F
// (after ~2000ms delay)
// B
// G
// H

// ─── STEP BY STEP ───
// 1. Sync Phase (Call Stack):
//    - "A" logs immediately.
//    - setTimeout #1 registers a 2000ms timer (macrotask).
//    - Promise.resolve().then(...) queues "C" in Microtask Queue.
//    - queueMicrotask(...) queues "D" in Microtask Queue.
//    - (async () => { ... })() runs synchronously:
//      * "E" logs immediately.
//      * `await null` wraps null into Promise.resolve(null), pauses the IIFE,
//        and queues the continuation ("F") into the Microtask Queue.
//    - setTimeout #2 registers another 2000ms timer (macrotask).
//
// 2. Microtask Queue Phase (drained before any macrotask):
//    - Microtask 1: logs "C"
//    - Microtask 2: logs "D"
//    - Microtask 3: resumes async IIFE β†’ logs "F"
//
// 3. Macrotask Phase (after ~2000ms):
//    - Timer #1 callback runs β†’ logs "B"
//    - Timer #2 callback runs β†’ logs "G", schedules "H" to Microtask Queue
//    - Microtask queue drained immediately before next macrotask β†’ logs "H"
//
// ─── KEY INTERVIEW CONCEPTS ───
// - `await <expr>` always yields control back to the caller and places the rest of the function in the Microtask Queue.
// - Async functions execute SYNCHRONOUSLY until the first `await`.
// - `queueMicrotask()` and `Promise.resolve().then()` push into the same microtask queue (FIFO).

πŸ”₯ 4. this Keyword β€” Dynamic vs Lexical Binding

Q4.1: Lost this Context

const obj = {
  name: 'Aditya',
  greet: function() {
    console.log(this.name);
  }
};

obj.greet();            // ?  β†’ "Aditya"   (this = obj, method invocation)

const fn = obj.greet;   // Extracting the method β€” loses context
fn();                   // ?  β†’ undefined   (this = global/window, standalone call)

// ─── WHY? ───
// In JavaScript, `this` is determined by HOW the function is CALLED, not where it's defined.
// obj.greet() β†’ called on obj β†’ this = obj
// fn()       β†’ called standalone β†’ this = global object (undefined in strict mode)

Q4.2: Arrow Function this

const obj = {
  name: 'Aditya',
  
  // Regular function β€” `this` is the object
  greet: function() {
    console.log('greet:', this.name);
  },
  
  // Arrow function β€” `this` is LEXICALLY inherited (from surrounding scope)
  greetArrow: () => {
    console.log('arrow:', this.name);
  },
  
  // Arrow inside a method β€” inherits `this` from the enclosing method
  delayedGreet: function() {
    setTimeout(() => {
      console.log('delayed:', this.name);
    }, 100);
  }
};

obj.greet();         // "greet: Aditya"    β†’ regular function, this = obj
obj.greetArrow();    // "arrow: undefined" β†’ arrow inherits from module/global scope
obj.delayedGreet();  // "delayed: Aditya"  β†’ arrow inherits this from delayedGreet (which is obj)

// ─── KEY RULE ───
// Arrow functions do NOT have their own `this`.
// They capture `this` from the ENCLOSING LEXICAL SCOPE at the time they are DEFINED.
// This is why arrow functions are great for callbacks inside methods.

Q4.3: this with call, bind, apply

function introduce(greeting, punctuation) {
  console.log(`${greeting}, I'm ${this.name}${punctuation}`);
}

const person = { name: 'Aditya' };

// call β€” invokes immediately, arguments passed individually
introduce.call(person, 'Hello', '!');       // "Hello, I'm Aditya!"

// apply β€” invokes immediately, arguments passed as an array
introduce.apply(person, ['Hi', '!!']);      // "Hi, I'm Aditya!!"

// bind β€” returns a NEW function with `this` permanently bound (does NOT invoke)
const boundFn = introduce.bind(person, 'Hey');
boundFn('...');                             // "Hey, I'm Aditya..."

// ─── MNEMONIC ───
// call β†’ C for Comma-separated args
// apply β†’ A for Array of args
// bind β†’ B for Bound (returns new function, doesn't call)

Q4.4: this Inside a Class

class User {
  constructor(name) {
    this.name = name;
  }

  greet() {
    console.log(`Hi, ${this.name}`);
  }

  greetArrow = () => {
    console.log(`Hi, ${this.name}`);
  };
}

const user = new User('Aditya');
user.greet();              // "Hi, Aditya" βœ…

const greetFn = user.greet;
greetFn();                 // TypeError: Cannot read properties of undefined 
                           // (strict mode in classes β€” `this` is undefined)

const greetArrowFn = user.greetArrow;
greetArrowFn();            // "Hi, Aditya" βœ… β€” arrow function captures `this` from constructor

// ─── INTERVIEW TIP ───
// Class field arrow functions (greetArrow = () => {}) are the safest way to
// pass class methods as callbacks without losing `this`.
// Used heavily in React class components: onClick={this.handleClick}

πŸ”₯ 5. Type Coercion β€” JavaScript’s Weird Parts

Q5.1: The Classics

console.log(1 + '2');        // "12"      ← number + string = string concatenation
console.log('5' - 3);        // 2         ← string - number = numeric subtraction
console.log('5' + 3);        // "53"      ← string + number = string concatenation
console.log('5' * '2');      // 10        ← both coerced to numbers for multiplication
console.log(true + true);    // 2         ← true is 1, so 1 + 1 = 2
console.log(true + false);   // 1         ← 1 + 0 = 1
console.log([] + []);        // ""        ← both arrays coerced to "" (empty string)
console.log([] + {});        // "[object Object]" ← "" + "[object Object]"
console.log({} + []);        // 0 or "[object Object]" 
                             //   ← depends on context! In console, {} is treated as empty block
console.log(null + 1);       // 1         ← null coerces to 0
console.log(undefined + 1);  // NaN       ← undefined coerces to NaN

// ─── THE RULE ───
// + with a string β†’ concatenation
// -, *, /, % β†’ always numeric conversion
// null β†’ 0, undefined β†’ NaN, true β†’ 1, false β†’ 0

Q5.2: Equality Gotchas

console.log(0 == false);     // true   ← false coerces to 0
console.log(0 == '');        // true   ← '' coerces to 0
console.log('' == false);    // true   ← both coerce to 0
console.log(null == undefined); // true ← special rule in the spec
console.log(null === undefined); // false ← different types
console.log(NaN == NaN);    // false  ← NaN is NOT equal to anything, including itself!
console.log(NaN === NaN);   // false  ← same reason

// ─── BEST PRACTICE ───
// ALWAYS use === (strict equality) to avoid implicit coercion surprises.
// The only exception: null == undefined is a useful check for "is this null or undefined?"

Q5.3: typeof Surprises

console.log(typeof undefined);   // "undefined"
console.log(typeof null);        // "object"  ← FAMOUS BUG! null is NOT an object
console.log(typeof NaN);         // "number"  ← NaN is technically a number type
console.log(typeof []);          // "object"  ← arrays are objects
console.log(typeof function(){}); // "function" ← functions get their own typeof
console.log(typeof typeof 1);   // "string"  ← typeof 1 = "number", typeof "number" = "string"

// ─── HOW TO PROPERLY CHECK ───
// Array:  Array.isArray([])     β†’ true
// null:   value === null        β†’ true
// NaN:    Number.isNaN(value)   β†’ true (don't use isNaN() β€” it coerces!)

πŸ”₯ 6. Scope Chain & Variable Shadowing

Q6.1: Shadowing

var x = 10;
let y = 20;

function test() {
  var x = 30;   // Shadows the outer x (both are var β€” function-scoped)
  let y = 40;   // Shadows the outer y (block-scoped to this function)
  console.log(x); // 30
  console.log(y); // 40
}

test();
console.log(x); // 10 ← outer x is unchanged
console.log(y); // 20 ← outer y is unchanged

Q6.2: Block Scope vs Function Scope

{
  var a = 1;    // var ignores blocks β€” goes to function/global scope
  let b = 2;   // let is block-scoped β€” stays inside { }
  const c = 3; // const is block-scoped β€” stays inside { }
}

console.log(a); // 1            ← var leaked out of the block
console.log(b); // ReferenceError ← let stays in the block
console.log(c); // ReferenceError ← const stays in the block

πŸ”₯ 7. Prototype Chain

Q7.1: Prototype Lookup

function Person(name) {
  this.name = name;
}

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

const john = new Person('John');

console.log(john.greet());          // "Hi, I'm John"
console.log(john.hasOwnProperty('name'));   // true  ← own property
console.log(john.hasOwnProperty('greet')); // false ← on prototype, not on john itself

// ─── PROTOTYPE CHAIN ───
// john β†’ Person.prototype β†’ Object.prototype β†’ null
//
// When you access john.greet():
// 1. JS checks john itself β€” no greet property
// 2. JS checks john.__proto__ (Person.prototype) β€” found greet! Use it.
// 3. If not found, would check Object.prototype, then null (gives undefined)

Q7.2: Modifying Prototype After Creation

function Animal(type) {
  this.type = type;
}

const dog = new Animal('Dog');

// Add method to prototype AFTER creating the instance
Animal.prototype.speak = function() {
  return `${this.type} speaks!`;
};

console.log(dog.speak()); // "Dog speaks!" ← Works! Prototype is a live reference.

// ─── WHY? ───
// dog.__proto__ is a REFERENCE to Animal.prototype.
// When we add speak() to Animal.prototype, dog can find it through the chain.
// Prototypes are looked up at ACCESS TIME, not creation time.

πŸ”₯ 8. Promises β€” Deep Dive

Q8.1: Promise Execution Order

const promise = new Promise((resolve) => {
  console.log('1');   // Executor runs SYNCHRONOUSLY
  resolve();
  console.log('2');   // This still runs! resolve() doesn't stop execution
});

promise.then(() => console.log('3'));  // Microtask
console.log('4');                       // Sync

// ─── OUTPUT ───
// 1
// 2
// 4
// 3

// ─── WHY? ───
// The Promise executor is sync β†’ logs 1, then calls resolve(), then logs 2
// resolve() does NOT stop the executor β€” it just marks the promise as resolved
// .then callback goes to microtask queue β†’ executed after all sync code
// Sync "4" runs, then microtask "3" runs

Q8.2: Promise Chaining β€” Return Values

Promise.resolve(1)
  .then(val => {
    console.log(val);      // 1
    return val + 1;        // Return value becomes the next .then's argument
  })
  .then(val => {
    console.log(val);      // 2
    // No return β†’ next .then receives undefined
  })
  .then(val => {
    console.log(val);      // undefined
    return Promise.resolve(10); // Can return a Promise too
  })
  .then(val => {
    console.log(val);      // 10 ← Promise is unwrapped automatically
  });

// ─── OUTPUT ───
// 1
// 2
// undefined
// 10

Q8.3: Promise.all vs Promise.allSettled vs Promise.race

const p1 = Promise.resolve('A');
const p2 = Promise.reject('Error!');
const p3 = Promise.resolve('C');

// Promise.all β€” rejects if ANY promise rejects
Promise.all([p1, p2, p3])
  .then(values => console.log(values))
  .catch(err => console.log('all:', err));
// Output: "all: Error!"

// Promise.allSettled β€” waits for ALL, never rejects
Promise.allSettled([p1, p2, p3])
  .then(results => console.log(results));
// Output: [
//   { status: 'fulfilled', value: 'A' },
//   { status: 'rejected', reason: 'Error!' },
//   { status: 'fulfilled', value: 'C' }
// ]

// Promise.race β€” resolves/rejects with the FIRST settled promise
Promise.race([p1, p2, p3])
  .then(val => console.log('race:', val))
  .catch(err => console.log('race error:', err));
// Output: "race: A" (p1 resolves first since they're all instant)

πŸ”₯ 9. Destructuring, Spread, Rest β€” Tricky Cases

Q9.1: Swap Without Temp Variable

let a = 1, b = 2;
[a, b] = [b, a];
console.log(a, b); // 2 1

// ─── HOW? ───
// Array destructuring: the right side creates [2, 1]
// The left side destructures it into a = 2, b = 1

Q9.2: Default Values with Destructuring

const { name = 'Guest', age = 0, role = 'user' } = { name: 'Aditya', age: 25 };
console.log(name); // "Aditya"  ← value exists, default ignored
console.log(age);  // 25        ← value exists, default ignored
console.log(role); // "user"    ← value missing, default used

// ─── GOTCHA: undefined vs null ───
const { x = 10 } = { x: undefined }; // x = 10 (default is used for undefined)
const { y = 10 } = { y: null };      // y = null (default is NOT used for null)

Q9.3: Rest and Spread

// Spread β€” expands elements
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 }; // { a: 1, b: 2, c: 3 }

// Rest β€” collects remaining elements
const [first, ...rest] = [1, 2, 3, 4];
console.log(first); // 1
console.log(rest);  // [2, 3, 4]

const { a, ...others } = { a: 1, b: 2, c: 3 };
console.log(a);      // 1
console.log(others); // { b: 2, c: 3 }

// ─── GOTCHA: Spread creates SHALLOW copies ───
const original = { x: 1, nested: { y: 2 } };
const copy = { ...original };
copy.nested.y = 99;
console.log(original.nested.y); // 99 ← MUTATED! Shallow copy shares nested references.

πŸ”₯ 10. Miscellaneous Tricky Questions

Q10.1: ++ Operator β€” Pre vs Post

let x = 1;
console.log(x++);  // 1  ← returns CURRENT value, THEN increments
console.log(x);    // 2  ← now x is 2

let y = 1;
console.log(++y);  // 2  ← increments FIRST, THEN returns
console.log(y);    // 2

Q10.2: Short-circuit Evaluation

console.log(0 || 'hello');     // "hello"  ← 0 is falsy, returns second operand
console.log(1 || 'hello');     // 1        ← 1 is truthy, returns first operand
console.log(0 && 'hello');     // 0        ← 0 is falsy, short-circuits
console.log(1 && 'hello');     // "hello"  ← 1 is truthy, returns second operand
console.log(null ?? 'default'); // "default" ← ?? (nullish coalescing) only checks null/undefined
console.log(0 ?? 'default');   // 0        ← 0 is NOT null/undefined, so returns 0
console.log('' ?? 'default');  // ""       ← '' is NOT null/undefined

// ─── KEY DIFFERENCE ───
// || returns the first TRUTHY value (treats 0, '', false as falsy)
// ?? returns the first NON-NULLISH value (only null/undefined are nullish)

Q10.3: delete Operator

var a = 1;        // Variables declared with var can't be deleted
let b = 2;        // let/const also can't be deleted
window.c = 3;     // Properties added directly to window CAN be deleted

console.log(delete a); // false (var can't be deleted)
console.log(delete b); // false (let can't be deleted)  
console.log(delete c); // true  (property deleted)

// ─── INTERVIEW TIP ───
// `delete` only works on object properties, not on variables.

Q10.4: Comma Operator

const result = (1, 2, 3, 4, 5);
console.log(result); // 5

// ─── WHY? ───
// The comma operator evaluates each operand left to right
// and returns the value of the LAST operand.

Q10.5: Tagged Template Literals

function tag(strings, ...values) {
  console.log(strings); // ['Hello ', ' you are ', '']
  console.log(values);  // ['Aditya', 25]
}

const name = 'Aditya';
const age = 25;
tag`Hello ${name} you are ${age}`;

// ─── HOW? ───
// Tagged templates pass the template parts as an array of strings
// and the interpolated values as additional arguments.
// Used in libraries like styled-components and GraphQL (gql`...`)

PART 2: JavaScript Coding Challenges (Write the Code)


πŸ’‘ 1. Polyfill for Array.prototype.map

// map() creates a new array by calling a function on every element
// Signature: array.map(callback(element, index, array), thisArg)

Array.prototype.myMap = function(callback, thisArg) {
  // 'this' refers to the array on which myMap is called
  if (typeof callback !== 'function') {
    throw new TypeError(callback + ' is not a function');
  }

  const result = [];
  for (let i = 0; i < this.length; i++) {
    // Only call callback for existing elements (handle sparse arrays)
    if (i in this) {
      result[i] = callback.call(thisArg, this[i], i, this);
    }
  }
  return result;
};

// Test
console.log([1, 2, 3].myMap(x => x * 2)); // [2, 4, 6]

πŸ’‘ 2. Polyfill for Array.prototype.filter

Array.prototype.myFilter = function(callback, thisArg) {
  if (typeof callback !== 'function') {
    throw new TypeError(callback + ' is not a function');
  }

  const result = [];
  for (let i = 0; i < this.length; i++) {
    if (i in this) {
      // Only push elements where callback returns truthy
      if (callback.call(thisArg, this[i], i, this)) {
        result.push(this[i]);
      }
    }
  }
  return result;
};

// Test
console.log([1, 2, 3, 4, 5].myFilter(x => x % 2 === 0)); // [2, 4]

πŸ’‘ 3. Polyfill for Array.prototype.reduce

Array.prototype.myReduce = function(callback, initialValue) {
  if (typeof callback !== 'function') {
    throw new TypeError(callback + ' is not a function');
  }

  let accumulator;
  let startIndex;

  if (initialValue !== undefined) {
    // If initialValue provided, start from index 0
    accumulator = initialValue;
    startIndex = 0;
  } else {
    // If no initialValue, use first element as accumulator, start from index 1
    if (this.length === 0) {
      throw new TypeError('Reduce of empty array with no initial value');
    }
    accumulator = this[0];
    startIndex = 1;
  }

  for (let i = startIndex; i < this.length; i++) {
    if (i in this) {
      accumulator = callback(accumulator, this[i], i, this);
    }
  }

  return accumulator;
};

// Test
console.log([1, 2, 3, 4].myReduce((acc, curr) => acc + curr, 0)); // 10
console.log([1, 2, 3, 4].myReduce((acc, curr) => acc + curr));    // 10

πŸ’‘ 4. Debounce Implementation

// Debounce: delays execution until user STOPS triggering for `delay` ms
// Use case: search input β€” wait until user stops typing before making API call

function debounce(fn, delay) {
  let timerId;

  return function(...args) {
    // Clear any existing timer (resets the delay)
    clearTimeout(timerId);

    // Set a new timer
    timerId = setTimeout(() => {
      fn.apply(this, args); // Preserve `this` context and arguments
    }, delay);
  };
}

// Usage
const search = debounce((query) => {
  console.log('Searching for:', query);
}, 300);

// If called rapidly, only the LAST call executes (after 300ms of silence)
search('r');
search('re');
search('rea');
search('reac');
search('react'); // Only this one fires β†’ "Searching for: react"

πŸ’‘ 5. Throttle Implementation

// Throttle: ensures function runs at most ONCE per `limit` ms
// Use case: scroll events β€” don't fire handler on every pixel scrolled

function throttle(fn, limit) {
  let inThrottle = false;

  return function(...args) {
    if (!inThrottle) {
      fn.apply(this, args);   // Execute the function
      inThrottle = true;       // Lock the gate

      setTimeout(() => {
        inThrottle = false;    // Unlock after `limit` ms
      }, limit);
    }
  };
}

// Usage
const handleScroll = throttle(() => {
  console.log('Scroll event handled at:', Date.now());
}, 1000);

// Even if scroll fires 100 times per second, handleScroll runs at most once per second

πŸ’‘ 6. Currying

// Currying: transforms f(a, b, c) into f(a)(b)(c)
// Each call returns a new function until all arguments are collected

function curry(fn) {
  return function curried(...args) {
    // If we have enough arguments, call the original function
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    }
    // Otherwise, return a new function that collects more arguments
    return function(...nextArgs) {
      return curried.apply(this, args.concat(nextArgs));
    };
  };
}

// Test
function add(a, b, c) {
  return a + b + c;
}

const curriedAdd = curry(add);

console.log(curriedAdd(1)(2)(3));    // 6
console.log(curriedAdd(1, 2)(3));    // 6
console.log(curriedAdd(1)(2, 3));    // 6
console.log(curriedAdd(1, 2, 3));    // 6  ← all forms work!

πŸ’‘ 7. Deep Flatten Array

// Flatten: [[1, [2]], [3, [4, [5]]]] β†’ [1, 2, 3, 4, 5]

// Method 1: Recursive
function flatten(arr) {
  return arr.reduce((acc, item) => {
    // If item is an array, recursively flatten it
    // If not, just add it to the accumulator
    return acc.concat(Array.isArray(item) ? flatten(item) : item);
  }, []);
}

// Method 2: Iterative with stack (no recursion limit)
function flattenIterative(arr) {
  const stack = [...arr]; // Copy to avoid mutating original
  const result = [];

  while (stack.length) {
    const item = stack.pop();
    if (Array.isArray(item)) {
      stack.push(...item); // Spread array items back onto stack
    } else {
      result.unshift(item); // Add to front (since we're popping from end)
    }
  }

  return result;
}

// Method 3: Built-in (ES2019)
console.log([1, [2, [3, [4]]]].flat(Infinity)); // [1, 2, 3, 4]

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

πŸ’‘ 8. Polyfill for Promise.all

// Promise.all: takes an array of promises, resolves when ALL resolve, rejects if ANY rejects

function myPromiseAll(promises) {
  return new Promise((resolve, reject) => {
    const results = [];
    let completed = 0;

    // Handle empty array
    if (promises.length === 0) {
      resolve([]);
      return;
    }

    promises.forEach((promise, index) => {
      // Promise.resolve() wraps non-promise values
      Promise.resolve(promise)
        .then(value => {
          results[index] = value; // Maintain order (don't use push!)
          completed++;

          // When ALL promises have resolved
          if (completed === promises.length) {
            resolve(results);
          }
        })
        .catch(reject); // If ANY promise rejects, reject the whole thing
    });
  });
}

// Test
myPromiseAll([
  Promise.resolve(1),
  Promise.resolve(2),
  Promise.resolve(3)
]).then(console.log); // [1, 2, 3]

myPromiseAll([
  Promise.resolve(1),
  Promise.reject('Error!'),
  Promise.resolve(3)
]).catch(console.log); // "Error!"

πŸ’‘ 9. Deep Clone

// Deep clone: creates a completely independent copy (no shared references)

function deepClone(obj) {
  // Handle null, undefined, and primitives
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }

  // Handle Date
  if (obj instanceof Date) {
    return new Date(obj.getTime());
  }

  // Handle RegExp
  if (obj instanceof RegExp) {
    return new RegExp(obj.source, obj.flags);
  }

  // Handle Array
  if (Array.isArray(obj)) {
    return obj.map(item => deepClone(item));
  }

  // Handle Object
  const cloned = {};
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      cloned[key] = deepClone(obj[key]); // Recursively clone each property
    }
  }
  return cloned;
}

// Test
const original = {
  name: 'Aditya',
  scores: [1, 2, 3],
  address: { city: 'Delhi', pin: { code: '110001' } }
};

const clone = deepClone(original);
clone.address.pin.code = '400001';
console.log(original.address.pin.code); // "110001" ← original unchanged!

// ─── QUICK ALTERNATIVE (with limitations) ───
// structuredClone(obj)  β€” built-in, handles most types (not functions/DOM)
// JSON.parse(JSON.stringify(obj)) β€” doesn't handle Date, RegExp, undefined, functions

πŸ’‘ 10. Memoize Function

// Memoize: caches function results so repeated calls with same args skip computation

function memoize(fn) {
  const cache = new Map(); // Map supports any key type

  return function(...args) {
    const key = JSON.stringify(args); // Serialize args as cache key

    if (cache.has(key)) {
      console.log('Cache hit for:', key);
      return cache.get(key);
    }

    console.log('Computing for:', key);
    const result = fn.apply(this, args);
    cache.set(key, result);
    return result;
  };
}

// Test
const factorial = memoize(function(n) {
  if (n <= 1) return 1;
  return n * factorial(n - 1);
});

console.log(factorial(5)); // Computing... β†’ 120
console.log(factorial(5)); // Cache hit! β†’ 120
console.log(factorial(3)); // Cache hit! β†’ 6 (computed during factorial(5))

πŸ’‘ 11. Compose & Pipe

// compose: right-to-left function composition
// pipe: left-to-right function composition

// compose(f, g, h)(x) = f(g(h(x)))
function compose(...fns) {
  return function(x) {
    return fns.reduceRight((acc, fn) => fn(acc), x);
  };
}

// pipe(f, g, h)(x) = h(g(f(x)))
function pipe(...fns) {
  return function(x) {
    return fns.reduce((acc, fn) => fn(acc), x);
  };
}

// Test
const add10 = x => x + 10;
const multiply2 = x => x * 2;
const subtract5 = x => x - 5;

const composed = compose(subtract5, multiply2, add10);
console.log(composed(5)); // add10(5)=15 β†’ multiply2(15)=30 β†’ subtract5(30)=25

const piped = pipe(add10, multiply2, subtract5);
console.log(piped(5));    // add10(5)=15 β†’ multiply2(15)=30 β†’ subtract5(30)=25
// Same result here, but the ORDER of functions is reversed!

πŸ’‘ 12. Event Emitter (Pub/Sub Pattern)

class EventEmitter {
  constructor() {
    this.events = {}; // { eventName: [callback1, callback2, ...] }
  }

  // Subscribe to an event
  on(event, callback) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(callback);
    return this; // Enable chaining
  }

  // Subscribe once β€” auto-removes after first call
  once(event, callback) {
    const wrapper = (...args) => {
      callback(...args);
      this.off(event, wrapper); // Remove after first execution
    };
    this.on(event, wrapper);
    return this;
  }

  // Emit an event β€” calls all subscribers
  emit(event, ...args) {
    if (this.events[event]) {
      this.events[event].forEach(cb => cb(...args));
    }
    return this;
  }

  // Unsubscribe
  off(event, callback) {
    if (this.events[event]) {
      this.events[event] = this.events[event].filter(cb => cb !== callback);
    }
    return this;
  }
}

// Test
const emitter = new EventEmitter();

emitter.on('greet', (name) => console.log(`Hello, ${name}!`));
emitter.once('greet', (name) => console.log(`Welcome, ${name}! (once)`));

emitter.emit('greet', 'Aditya');
// "Hello, Aditya!"
// "Welcome, Aditya! (once)"

emitter.emit('greet', 'Aditya');
// "Hello, Aditya!"
// (once listener is already removed)

PART 3: React Coding Challenges


βš›οΈ 1. Todo App (The #1 Interview Question)

import { useState } from 'react';

function TodoApp() {
  const [todos, setTodos] = useState([]);        // Array of todo objects
  const [input, setInput] = useState('');          // Current input text

  // Add a new todo
  const addTodo = () => {
    if (!input.trim()) return;                     // Guard: don't add empty todos
    setTodos(prev => [
      ...prev,                                     // Spread existing todos
      { id: Date.now(), text: input, done: false } // New todo with unique id
    ]);
    setInput('');                                   // Clear input after adding
  };

  // Toggle completion status
  const toggleTodo = (id) => {
    setTodos(prev =>
      prev.map(todo =>
        todo.id === id
          ? { ...todo, done: !todo.done }           // Flip the done flag
          : todo                                     // Leave others unchanged
      )
    );
  };

  // Delete a todo
  const deleteTodo = (id) => {
    setTodos(prev => prev.filter(todo => todo.id !== id));
    // filter returns NEW array without the matching todo
  };

  return (
    <div>
      <h1>Todo App</h1>
      <input
        value={input}
        onChange={(e) => setInput(e.target.value)}
        onKeyDown={(e) => e.key === 'Enter' && addTodo()} // Add on Enter key
        placeholder="Add a task..."
      />
      <button onClick={addTodo}>Add</button>

      <ul>
        {todos.map(todo => (
          <li key={todo.id} style={{ textDecoration: todo.done ? 'line-through' : 'none' }}>
            <input
              type="checkbox"
              checked={todo.done}
              onChange={() => toggleTodo(todo.id)}
            />
            {todo.text}
            <button onClick={() => deleteTodo(todo.id)}>❌</button>
          </li>
        ))}
      </ul>

      {/* Stats */}
      <p>Total: {todos.length} | Done: {todos.filter(t => t.done).length}</p>
    </div>
  );
}

βš›οΈ 2. Search with Debouncing (Autocomplete)

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

// Custom hook for debouncing
function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => setDebouncedValue(value), delay);
    return () => clearTimeout(timer);  // Cleanup: cancel timer if value changes again
  }, [value, delay]);

  return debouncedValue;
}

function SearchAutocomplete() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [loading, setLoading] = useState(false);

  const debouncedQuery = useDebounce(query, 300); // Wait 300ms after user stops typing

  useEffect(() => {
    if (!debouncedQuery) {
      setResults([]);
      return;
    }

    const controller = new AbortController(); // Cancel previous request on new search
    setLoading(true);

    fetch(`https://api.example.com/search?q=${debouncedQuery}`, {
      signal: controller.signal
    })
      .then(res => res.json())
      .then(data => {
        setResults(data);
        setLoading(false);
      })
      .catch(err => {
        if (err.name !== 'AbortError') setLoading(false);
      });

    return () => controller.abort(); // Cleanup: cancel fetch on new query
  }, [debouncedQuery]);

  return (
    <div>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Search..."
      />
      {loading && <p>Loading...</p>}
      <ul>
        {results.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
}

βš›οΈ 3. Star Rating Component

import { useState } from 'react';

function StarRating({ maxStars = 5, onChange }) {
  const [rating, setRating] = useState(0);     // Selected rating
  const [hover, setHover] = useState(0);       // Hovered star (for preview)

  const handleClick = (starIndex) => {
    setRating(starIndex);
    onChange?.(starIndex); // Call parent callback if provided
  };

  return (
    <div style={{ display: 'flex', cursor: 'pointer', fontSize: '2rem' }}>
      {/* Create an array of length maxStars and map over it */}
      {Array.from({ length: maxStars }, (_, i) => i + 1).map(star => (
        <span
          key={star}
          onClick={() => handleClick(star)}
          onMouseEnter={() => setHover(star)}
          onMouseLeave={() => setHover(0)}
          style={{
            // Fill star if it's <= the hovered value (or rating if not hovering)
            color: star <= (hover || rating) ? '#ffc107' : '#e0e0e0',
            transition: 'color 0.2s'
          }}
        >
          β˜…
        </span>
      ))}
      <span style={{ marginLeft: '8px', fontSize: '1rem' }}>
        {rating}/{maxStars}
      </span>
    </div>
  );
}

// Usage: <StarRating maxStars={5} onChange={(val) => console.log(val)} />

βš›οΈ 4. Accordion Component

import { useState } from 'react';

function AccordionItem({ title, content, isOpen, onToggle }) {
  return (
    <div style={{ border: '1px solid #ddd', marginBottom: '4px', borderRadius: '4px' }}>
      <button
        onClick={onToggle}
        style={{
          width: '100%', padding: '12px', textAlign: 'left',
          background: isOpen ? '#f0f0f0' : 'white', cursor: 'pointer',
          border: 'none', fontWeight: 'bold', fontSize: '16px'
        }}
      >
        {title} {isOpen ? 'β–²' : 'β–Ό'}
      </button>
      {/* Conditionally render content β€” this is the accordion magic */}
      {isOpen && (
        <div style={{ padding: '12px', borderTop: '1px solid #ddd' }}>
          {content}
        </div>
      )}
    </div>
  );
}

function Accordion({ items, allowMultiple = false }) {
  // If allowMultiple, track a Set of open indices; otherwise, track a single index
  const [openIndices, setOpenIndices] = useState(new Set());

  const toggleItem = (index) => {
    setOpenIndices(prev => {
      const next = new Set(allowMultiple ? prev : []); // Reset if single-mode
      if (next.has(index)) {
        next.delete(index);  // Close if already open
      } else {
        next.add(index);     // Open if closed
      }
      return next;
    });
  };

  return (
    <div>
      {items.map((item, index) => (
        <AccordionItem
          key={index}
          title={item.title}
          content={item.content}
          isOpen={openIndices.has(index)}
          onToggle={() => toggleItem(index)}
        />
      ))}
    </div>
  );
}

// Usage
const faqData = [
  { title: 'What is React?', content: 'A JavaScript library for building UIs.' },
  { title: 'What is JSX?', content: 'A syntax extension for JavaScript.' },
  { title: 'What are Hooks?', content: 'Functions to use state in functional components.' },
];

// <Accordion items={faqData} allowMultiple={false} />

βš›οΈ 5. Infinite Scroll

import { useState, useEffect, useRef, useCallback } from 'react';

function InfiniteScroll() {
  const [items, setItems] = useState([]);
  const [page, setPage] = useState(1);
  const [loading, setLoading] = useState(false);
  const [hasMore, setHasMore] = useState(true);

  // Intersection Observer ref β€” attaches to the last element
  const observer = useRef();

  // This ref callback is assigned to the LAST item in the list
  const lastItemRef = useCallback(
    (node) => {
      if (loading) return;                    // Don't observe while loading
      if (observer.current) observer.current.disconnect(); // Cleanup previous observer

      observer.current = new IntersectionObserver((entries) => {
        // When the last item becomes visible AND there's more data
        if (entries[0].isIntersecting && hasMore) {
          setPage(prev => prev + 1);          // Trigger next page load
        }
      });

      if (node) observer.current.observe(node); // Start observing the new last element
    },
    [loading, hasMore]
  );

  // Fetch data whenever page changes
  useEffect(() => {
    setLoading(true);

    fetch(`https://api.example.com/items?page=${page}&limit=20`)
      .then(res => res.json())
      .then(data => {
        setItems(prev => [...prev, ...data.items]); // Append new items
        setHasMore(data.items.length > 0);           // No more items? Stop loading
        setLoading(false);
      })
      .catch(() => setLoading(false));
  }, [page]);

  return (
    <div>
      {items.map((item, index) => {
        // Attach ref to the LAST item only
        const isLast = index === items.length - 1;
        return (
          <div
            key={item.id}
            ref={isLast ? lastItemRef : null}
            style={{ padding: '16px', borderBottom: '1px solid #eee' }}
          >
            {item.name}
          </div>
        );
      })}

      {loading && <p>Loading more...</p>}
      {!hasMore && <p>No more items</p>}
    </div>
  );
}

βš›οΈ 6. Theme Switcher (Dark/Light Mode with Context)

import { createContext, useContext, useState, useCallback } from 'react';

// 1. Create Theme Context
const ThemeContext = createContext();

// 2. Theme Provider component
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

  const toggleTheme = useCallback(() => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  }, []);

  // Memoize context value to prevent unnecessary re-renders
  const value = { theme, toggleTheme };

  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
}

// 3. Custom hook for consuming theme
function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within a ThemeProvider');
  }
  return context;
}

// 4. Component that uses the theme
function ThemedApp() {
  const { theme, toggleTheme } = useTheme();

  const styles = {
    background: theme === 'dark' ? '#1a1a1a' : '#ffffff',
    color: theme === 'dark' ? '#ffffff' : '#000000',
    padding: '20px', minHeight: '100vh', transition: 'all 0.3s ease'
  };

  return (
    <div style={styles}>
      <h1>{theme === 'dark' ? 'πŸŒ™' : 'β˜€οΈ'} Theme: {theme}</h1>
      <button onClick={toggleTheme}>Toggle Theme</button>
    </div>
  );
}

// 5. App wrapper
function App() {
  return (
    <ThemeProvider>
      <ThemedApp />
    </ThemeProvider>
  );
}

βš›οΈ 7. Counter with useReducer (Complex State)

import { useReducer } from 'react';

// Define all possible actions
const ACTIONS = {
  INCREMENT: 'increment',
  DECREMENT: 'decrement',
  RESET: 'reset',
  SET: 'set'
};

// Reducer: pure function that takes state + action β†’ returns new state
function counterReducer(state, action) {
  switch (action.type) {
    case ACTIONS.INCREMENT:
      return { ...state, count: state.count + (action.payload || 1) };
    case ACTIONS.DECREMENT:
      return { ...state, count: state.count - (action.payload || 1) };
    case ACTIONS.RESET:
      return { ...state, count: 0, history: [] };
    case ACTIONS.SET:
      return { ...state, count: action.payload, history: [...state.history, state.count] };
    default:
      throw new Error(`Unknown action: ${action.type}`);
  }
}

function AdvancedCounter() {
  const [state, dispatch] = useReducer(counterReducer, { count: 0, history: [] });

  return (
    <div>
      <h1>Count: {state.count}</h1>
      <button onClick={() => dispatch({ type: ACTIONS.INCREMENT })}>+1</button>
      <button onClick={() => dispatch({ type: ACTIONS.INCREMENT, payload: 5 })}>+5</button>
      <button onClick={() => dispatch({ type: ACTIONS.DECREMENT })}>-1</button>
      <button onClick={() => dispatch({ type: ACTIONS.RESET })}>Reset</button>
      <button onClick={() => dispatch({ type: ACTIONS.SET, payload: 100 })}>Set to 100</button>

      <h3>History:</h3>
      <ul>
        {state.history.map((val, i) => <li key={i}>{val}</li>)}
      </ul>
    </div>
  );
}

βš›οΈ 8. File Explorer (Recursive Component)

import { useState } from 'react';

// Sample file system data
const fileSystemData = {
  name: 'root',
  type: 'folder',
  children: [
    {
      name: 'src',
      type: 'folder',
      children: [
        { name: 'App.jsx', type: 'file' },
        { name: 'index.js', type: 'file' },
        {
          name: 'components',
          type: 'folder',
          children: [
            { name: 'Header.jsx', type: 'file' },
            { name: 'Footer.jsx', type: 'file' }
          ]
        }
      ]
    },
    { name: 'package.json', type: 'file' },
    { name: 'README.md', type: 'file' }
  ]
};

function FileExplorerItem({ item, depth = 0 }) {
  const [isOpen, setIsOpen] = useState(false);

  const isFolder = item.type === 'folder';

  return (
    <div style={{ paddingLeft: `${depth * 20}px` }}>
      <div
        onClick={() => isFolder && setIsOpen(prev => !prev)}
        style={{
          cursor: isFolder ? 'pointer' : 'default',
          padding: '4px 8px',
          userSelect: 'none'
        }}
      >
        {/* Icon based on type and open/closed state */}
        {isFolder ? (isOpen ? 'πŸ“‚' : 'πŸ“') : 'πŸ“„'} {item.name}
      </div>

      {/* Recursively render children if folder is open */}
      {isFolder && isOpen && item.children && (
        <div>
          {item.children.map((child, index) => (
            <FileExplorerItem
              key={`${child.name}-${index}`}
              item={child}
              depth={depth + 1}  // Increase indentation for nested items
            />
          ))}
        </div>
      )}
    </div>
  );
}

function FileExplorer() {
  return (
    <div style={{ fontFamily: 'monospace', fontSize: '14px' }}>
      <h3>πŸ“ File Explorer</h3>
      <FileExplorerItem item={fileSystemData} />
    </div>
  );
}

βš›οΈ 9. OTP Input Component

import { useState, useRef } from 'react';

function OTPInput({ length = 6, onComplete }) {
  const [otp, setOtp] = useState(new Array(length).fill(''));
  const inputRefs = useRef([]); // Array of refs for each input

  const handleChange = (index, value) => {
    // Only allow single digits
    if (value && !/^\d$/.test(value)) return;

    const newOtp = [...otp];
    newOtp[index] = value;
    setOtp(newOtp);

    // Auto-focus next input after typing a digit
    if (value && index < length - 1) {
      inputRefs.current[index + 1].focus();
    }

    // Check if OTP is complete
    const otpString = newOtp.join('');
    if (otpString.length === length && !newOtp.includes('')) {
      onComplete?.(otpString);
    }
  };

  const handleKeyDown = (index, e) => {
    // On Backspace, clear current and move focus to previous input
    if (e.key === 'Backspace') {
      if (otp[index] === '' && index > 0) {
        inputRefs.current[index - 1].focus(); // Move to previous
      }
    }
  };

  const handlePaste = (e) => {
    e.preventDefault();
    const pastedData = e.clipboardData.getData('text').slice(0, length);
    if (!/^\d+$/.test(pastedData)) return; // Only allow digits

    const newOtp = [...otp];
    pastedData.split('').forEach((char, i) => {
      newOtp[i] = char;
    });
    setOtp(newOtp);

    // Focus the next empty input or the last input
    const focusIndex = Math.min(pastedData.length, length - 1);
    inputRefs.current[focusIndex].focus();
  };

  return (
    <div style={{ display: 'flex', gap: '8px' }}>
      {otp.map((digit, index) => (
        <input
          key={index}
          ref={el => inputRefs.current[index] = el}
          type="text"
          inputMode="numeric"
          maxLength={1}
          value={digit}
          onChange={(e) => handleChange(index, e.target.value)}
          onKeyDown={(e) => handleKeyDown(index, e)}
          onPaste={index === 0 ? handlePaste : undefined} // Only first input handles paste
          style={{
            width: '40px', height: '48px', textAlign: 'center',
            fontSize: '20px', border: '2px solid #ccc', borderRadius: '8px'
          }}
        />
      ))}
    </div>
  );
}

// Usage: <OTPInput length={6} onComplete={(otp) => console.log('OTP:', otp)} />

βš›οΈ 10. Shopping Cart with Context

import { createContext, useContext, useReducer } from 'react';

// ─── Cart Context ───
const CartContext = createContext();

const cartReducer = (state, action) => {
  switch (action.type) {
    case 'ADD_ITEM': {
      const existing = state.items.find(i => i.id === action.payload.id);
      if (existing) {
        // If item exists, increment quantity
        return {
          ...state,
          items: state.items.map(i =>
            i.id === action.payload.id
              ? { ...i, quantity: i.quantity + 1 }
              : i
          )
        };
      }
      // If new item, add with quantity 1
      return { ...state, items: [...state.items, { ...action.payload, quantity: 1 }] };
    }

    case 'REMOVE_ITEM':
      return { ...state, items: state.items.filter(i => i.id !== action.payload) };

    case 'UPDATE_QUANTITY':
      return {
        ...state,
        items: state.items.map(i =>
          i.id === action.payload.id
            ? { ...i, quantity: Math.max(0, action.payload.quantity) }
            : i
        ).filter(i => i.quantity > 0) // Remove items with 0 quantity
      };

    case 'CLEAR_CART':
      return { ...state, items: [] };

    default:
      return state;
  }
};

function CartProvider({ children }) {
  const [state, dispatch] = useReducer(cartReducer, { items: [] });

  // Computed values
  const totalItems = state.items.reduce((sum, item) => sum + item.quantity, 0);
  const totalPrice = state.items.reduce((sum, item) => sum + item.price * item.quantity, 0);

  const value = {
    items: state.items,
    totalItems,
    totalPrice,
    addItem: (item) => dispatch({ type: 'ADD_ITEM', payload: item }),
    removeItem: (id) => dispatch({ type: 'REMOVE_ITEM', payload: id }),
    updateQuantity: (id, quantity) =>
      dispatch({ type: 'UPDATE_QUANTITY', payload: { id, quantity } }),
    clearCart: () => dispatch({ type: 'CLEAR_CART' }),
  };

  return <CartContext.Provider value={value}>{children}</CartContext.Provider>;
}

function useCart() {
  const context = useContext(CartContext);
  if (!context) throw new Error('useCart must be used within CartProvider');
  return context;
}

// ─── Product List ───
function ProductList() {
  const { addItem } = useCart();

  const products = [
    { id: 1, name: 'React Book', price: 29.99 },
    { id: 2, name: 'JS Course', price: 49.99 },
    { id: 3, name: 'Node Guide', price: 19.99 },
  ];

  return (
    <div>
      <h2>Products</h2>
      {products.map(product => (
        <div key={product.id} style={{ padding: '8px', border: '1px solid #ddd', margin: '4px' }}>
          <span>{product.name} β€” ${product.price}</span>
          <button onClick={() => addItem(product)}>Add to Cart</button>
        </div>
      ))}
    </div>
  );
}

// ─── Cart Display ───
function Cart() {
  const { items, totalItems, totalPrice, removeItem, updateQuantity, clearCart } = useCart();

  return (
    <div>
      <h2>Cart ({totalItems} items)</h2>
      {items.map(item => (
        <div key={item.id} style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
          <span>{item.name}</span>
          <button onClick={() => updateQuantity(item.id, item.quantity - 1)}>-</button>
          <span>{item.quantity}</span>
          <button onClick={() => updateQuantity(item.id, item.quantity + 1)}>+</button>
          <span>${(item.price * item.quantity).toFixed(2)}</span>
          <button onClick={() => removeItem(item.id)}>πŸ—‘οΈ</button>
        </div>
      ))}
      <h3>Total: ${totalPrice.toFixed(2)}</h3>
      <button onClick={clearCart}>Clear Cart</button>
    </div>
  );
}

// ─── App ───
function App() {
  return (
    <CartProvider>
      <ProductList />
      <Cart />
    </CartProvider>
  );
}

PART 4: Tricky Console Output Puzzles (Rapid Fire)

Try to answer BEFORE looking at the output. These are the ones that trip people up in interviews.


Puzzle 1

console.log(typeof typeof 1);
// Output: "string"
// typeof 1 = "number" (a string), typeof "number" = "string"

Puzzle 2

console.log(0.1 + 0.2 === 0.3);
// Output: false
// 0.1 + 0.2 = 0.30000000000000004 (floating point precision issue)
// Fix: Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON

Puzzle 3

let a = { x: 1 };
let b = a;        // b points to the SAME object as a
b.x = 2;          // Mutates the shared object
console.log(a.x); // 2 β€” a and b reference the same object

Puzzle 4

console.log([] == false);  // true  β€” both coerce to 0
console.log([] == ![]);    // true  β€” ![] is false, [] == false β†’ 0 == 0 β†’ true
console.log(!![] == true); // true  β€” !![] β†’ !false β†’ true

Puzzle 5

const arr = [1, 2, 3];
arr[10] = 11;
console.log(arr.length);  // 11 β€” JS creates "holes" (empty slots) for indices 3-9
console.log(arr[5]);       // undefined β€” empty slot returns undefined

Puzzle 6

function foo() {
  return
  {
    bar: 'hello'
  };
}
console.log(foo());
// Output: undefined
// JavaScript's ASI (Automatic Semicolon Insertion) adds a semicolon after `return`
// It becomes: return; { bar: 'hello' };
// Fix: put the opening { on the same line as return

Puzzle 7

let x = 1;
switch(x) {
  case 1:
    console.log('one');
  case 2:
    console.log('two');
  case 3:
    console.log('three');
}
// Output:
// "one"
// "two"
// "three"
// Without `break`, execution FALLS THROUGH to all subsequent cases!

Puzzle 8

console.log('5' - - '3');
// Output: 8
// The double negative: - '3' converts '3' to number and negates β†’ -3
// Then '5' - (-3) β†’ 5 + 3 β†’ 8

Puzzle 9

const a = {};
const b = { key: 'b' };
const c = { key: 'c' };

a[b] = 123;  // Object keys are strings β†’ a["[object Object]"] = 123
a[c] = 456;  // Same key! β†’ a["[object Object]"] = 456 (overwrites!)

console.log(a[b]); // 456
// Both b and c get converted to the same string "[object Object]"

Puzzle 10

var x = 10;
(function() {
  console.log(x); // ?
  var x = 20;
})();
// Output: undefined
// The inner `var x` is hoisted to the top of the IIFE
// So it becomes: var x; console.log(x); x = 20;
// The inner x shadows the outer x = 10

Puzzle 11

console.log(1 < 2 < 3);  // true  β€” (1 < 2) = true, (true < 3) = (1 < 3) = true
console.log(3 > 2 > 1);  // false β€” (3 > 2) = true, (true > 1) = (1 > 1) = false ⚠️

Puzzle 12

const person = { name: 'Aditya' };
Object.freeze(person);    // Prevents modifications

person.name = 'Modified'; // Silently fails (or throws in strict mode)
person.age = 25;           // Silently fails

console.log(person);       // { name: 'Aditya' } β€” unchanged!

// ⚠️ GOTCHA: freeze is SHALLOW
const obj = { nested: { x: 1 } };
Object.freeze(obj);
obj.nested.x = 99;        // This WORKS! Nested objects are NOT frozen.
console.log(obj.nested.x); // 99

Puzzle 13

const nums = [1, 2, 3];
const [a, , c] = nums;  // Skip element at index 1
console.log(a, c);       // 1 3

Puzzle 14

console.log(+'');        // 0     β€” empty string coerces to 0
console.log(+' ');       // 0     β€” whitespace string coerces to 0
console.log(+'hello');   // NaN   β€” non-numeric string
console.log(+true);      // 1
console.log(+false);     // 0
console.log(+null);      // 0
console.log(+undefined); // NaN
console.log(+[]);        // 0     β€” [] β†’ '' β†’ 0
console.log(+[1]);       // 1     β€” [1] β†’ '1' β†’ 1
console.log(+[1,2]);     // NaN   β€” [1,2] β†’ '1,2' β†’ NaN

Puzzle 15

async function getData() {
  return 'Hello';
}

console.log(getData());        // Promise { 'Hello' } β€” async always returns a Promise
console.log(await getData());  // "Hello" (only works at top-level in modules or inside async fn)
🏠 Back to Home