1. What is JavaScript primarily used for?
Difficulty: EasyType: MCQTopic: JS Basics
- Database management only
- Making websites interactive and building web applications
- Operating system development
- Only for server-side programming
JavaScript is a high-level programming language that’s primarily used to make web pages interactive and dynamic. In the browser, we use JavaScript to handle user actions, manipulate the DOM, validate forms, and update parts of the page without a full reload.
It’s also used on the server side with environments like Node.js, so the same programming language can be used for both frontend and backend development, which makes it really popular for building full-stack web applications.
Correct Answer: Making websites interactive and building web applications
2. What is the main difference between var and let?
Difficulty: MediumType: MCQTopic: Scope Hoisting
- var is faster than let
- var is function-scoped, let is block-scoped
- let cannot be reassigned but var can
- var is for strings, let is for numbers
The key difference is scope.
Var has function scope. It is accessible anywhere within the entire function, even outside blocks like if statements or loops. This causes variables to leak out of blocks.
Let has block scope. It only exists within the curly braces where it is defined. This is safer and prevents variable leaking.
Modern JavaScript always uses let and const over var.
Correct Answer: var is function-scoped, let is block-scoped
Example Code
// var - function scoped (leaks out of blocks)
function testVar() {
if (true) {
var x = 10; // Accessible everywhere in function
}
console.log(x); // 10 - Still accessible! (BAD)
}
// let - block scoped (stays inside block)
function testLet() {
if (true) {
let y = 10; // Only accessible in this block
}
console.log(y); // Error! y is not defined (GOOD)
}
// Real example - loop with var
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Prints: 3, 3, 3 (all reference same i)
// Same loop with let
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Prints: 0, 1, 2 (each has own i)3. What will be the output?
console.log(x);
var x = 5;
Difficulty: MediumType: MCQTopic: Scope Hoisting
- 5
- undefined
- ReferenceError: x is not defined
- null
The output is undefined because of hoisting.
Hoisting means JavaScript moves variable declarations to the top of their scope before running code. But it only moves the declaration, not the assignment.
What JavaScript actually does:
It moves var x to the top and initializes it as undefined. Then when you log x, it prints undefined. After that, x gets assigned the value 5.
Think of it like reading a book:
Before you start reading, you flip through and see all chapter titles first. JavaScript does the same with declarations. It sees them all first, then starts executing code.
Only var behaves this way. Let and const will throw an error if accessed before declaration.
Correct Answer: undefined
Example Code
// Example 1: var hoisting
console.log(name); // undefined (not error!)
var name = 'John';
console.log(name); // 'John'
// What JavaScript does internally:
var name; // Declaration hoisted
console.log(name); // undefined
name = 'John'; // Assignment stays in place
console.log(name); // 'John'
// Example 2: Multiple variables
console.log(a, b, c); // undefined undefined undefined
var a = 1;
var b = 2;
var c = 3;
// Example 3: In functions
function test() {
console.log(score); // undefined
var score = 100;
console.log(score); // 100
}4. What happens when you access a let variable before its declaration?
Difficulty: MediumType: MCQTopic: Scope Hoisting
- Returns undefined like var
- Returns null
- Throws ReferenceError
- Returns empty string
It throws a ReferenceError because of the Temporal Dead Zone.
The Temporal Dead Zone is the period from when you enter a scope until the variable is declared. During this time, the variable exists but you cannot access it.
Think of it like a restaurant opening:
The building exists, the sign is up, but doors are locked. You know it is there but cannot go inside yet.
Why does this exist:
JavaScript designers wanted let and const to be safer than var. The TDZ prevents you from using variables before they are initialized, which catches bugs early.
Key difference: Var is hoisted and initialized as undefined. Let and const are hoisted but stay uninitialized in the TDZ.
Correct Answer: Throws ReferenceError
Example Code
// let and const throw errors
console.log(age); // ReferenceError: Cannot access 'age' before initialization
let age = 25;
// var behaves differently
console.log(name); // undefined (no error)
var name = 'John';
// Temporal Dead Zone explained
{
// TDZ starts here for 'temp'
// You cannot access 'temp' here
console.log(temp); // ReferenceError!
let temp = 100; // TDZ ends here
// Now you can use 'temp'
console.log(temp); // 100 - Works fine
}
// Function example
function calculate() {
// TDZ for result
// console.log(result); // Would throw error
let result = 10 * 5; // TDZ ends
console.log(result); // 50
}5. How many primitive data types are there in JavaScript?
Difficulty: EasyType: MCQTopic: Data Types
JavaScript has exactly 7 primitive data types.
The 7 primitive types:
String for text like names and messages.
Number for all numbers including decimals.
BigInt for extremely large integers.
Boolean for true and false values.
Undefined for variables not yet assigned.
Null for intentional empty values.
Symbol for unique identifiers.
Memory trick: Some Nice Ugly Bears Bought New Shoes.
Or remember: String, Number, Undefined, Boolean, BigInt, Null, Symbol.
Primitives are immutable, stored by value, and are the basic building blocks of JavaScript.
Correct Answer: 7
Example Code
// All 7 primitive types
let text = 'Hello'; // String
let age = 25; // Number
let bigNumber = 123n; // BigInt (notice the 'n')
let isActive = true; // Boolean
let notAssigned; // undefined
let empty = null; // null
let id = Symbol('unique'); // Symbol
// Check types with typeof
console.log(typeof text); // 'string'
console.log(typeof age); // 'number'
console.log(typeof bigNumber); // 'bigint'
console.log(typeof isActive); // 'boolean'
console.log(typeof notAssigned);// 'undefined'
console.log(typeof empty); // 'object' (JavaScript bug!)
console.log(typeof id); // 'symbol'
// Everything else is an object
let arr = [1, 2, 3]; // object
let obj = {name: 'John'}; // object
let func = function() {}; // function (special object)6. What does typeof null return in JavaScript?
Difficulty: EasyType: MCQTopic: JS Operators
- null
- undefined
- object
- number
Typeof null returns object, but this is actually a bug in JavaScript.
Null is a primitive type, not an object. This bug has existed since JavaScript was created in 1995.
Why was it not fixed:
If JavaScript fixed this bug now, millions of websites would break. Many websites check if something is an object using typeof. Changing it would cause chaos.
How to correctly check for null:
Never use typeof to check for null. Use triple equals instead.
Write: if (value === null)
Correct Answer: object
Example Code
// The famous typeof null bug
console.log(typeof null); // 'object' (WRONG but cannot fix)
console.log(typeof undefined); // 'undefined' (correct)
// Other typeof examples
console.log(typeof 42); // 'number'
console.log(typeof 'hello'); // 'string'
console.log(typeof true); // 'boolean'
console.log(typeof {}); // 'object'
console.log(typeof []); // 'object' (arrays are objects)
// WRONG way to check for null
if (typeof value === 'object') {
// This could be null OR an object OR an array!
}
// CORRECT way to check for null
if (value === null) {
console.log('Value is null');
}
// Check for null or undefined
if (value == null) {
// True for both null and undefined
}
// Detailed null check
if (value !== null && typeof value === 'object') {
console.log('It is an object, not null');
}7. What is the difference between == and === in JavaScript?
Difficulty: EasyType: MCQTopic: JS Operators
- No difference, they are exactly the same
- == compares value only, === compares value and type
- === is newer but works the same as ==
- == is faster than ===
Double equals performs loose equality with type coercion. It converts values to the same type before comparing. This causes unexpected results.
Triple equals performs strict equality without type coercion. It compares both value and type. If types are different, it returns false immediately.
Simple rule:
Double equals is lenient, like a relaxed teacher. Triple equals is strict, like a strict teacher.
Always use triple equals unless you have a specific reason not to.
Real world analogy:
Comparing a 5 dollar bill and a 5 euro note. Double equals says equal because both are 5. Triple equals says not equal because one is dollars and one is euros.
Best practice: Always use triple equals. Most companies enforce this in coding standards.
Correct Answer: == compares value only, === compares value and type
Example Code
// == (loose equality with type coercion)
console.log(5 == '5'); // true (string converted to number)
console.log(true == 1); // true (boolean converted to number)
console.log(false == 0); // true
console.log(null == undefined); // true (special case)
console.log('' == 0); // true (empty string to 0)
// === (strict equality without coercion)
console.log(5 === '5'); // false (different types)
console.log(true === 1); // false (different types)
console.log(false === 0); // false (different types)
console.log(null === undefined); // false (different types)
console.log('' === 0); // false (different types)
// Dangerous examples with ==
console.log(0 == false); // true (confusing!)
console.log('' == false); // true (confusing!)
console.log([] == false); // true (very confusing!)
// Safe with ===
console.log(0 === false); // false (clear!)
console.log('' === false); // false (clear!)
console.log([] === false); // false (clear!)8. What is the key difference between null and undefined?
Difficulty: MediumType: MCQTopic: JS Operators
- They are exactly the same thing
- undefined is automatic, null is intentional
- null is for numbers, undefined is for strings
- undefined throws errors, null does not
Undefined is JavaScript's way of saying this variable exists but has no value yet.
JavaScript automatically assigns undefined when:
You declare a variable but do not initialize it.
You access an object property that does not exist.
A function has no return statement.
A function parameter is not provided.
Null is your way of saying this variable intentionally has no value. You explicitly assign null to represent emptiness.
Think of it like parking spaces:
Undefined is an empty space with no sign. Nothing has parked there yet.
Null is an empty space with a Reserved sign. You intentionally marked it as empty.
Both are falsy values but have different meanings.
Correct Answer: undefined is automatic, null is intentional
Example Code
// undefined - JavaScript sets it automatically
let a;
console.log(a); // undefined (variable declared but not assigned)
let person = { name: 'John' };
console.log(person.age); // undefined (property doesn't exist)
function test() {
// no return statement
}
console.log(test()); // undefined (no return value)
function greet(name) {
console.log(name); // undefined if not passed
}
greet(); // undefined (parameter not provided)
// null - You set it intentionally
let user = null; // Intentionally empty
let data = null; // Waiting for data
let response = null; // No response yet
// Checking for both
console.log(typeof undefined); // 'undefined'
console.log(typeof null); // 'object' (bug)
console.log(undefined == null); // true (loose equality)
console.log(undefined === null); // false (different types)
// Practical usage
if (value === undefined) {
console.log('Never assigned');
}
if (value === null) {
console.log('Intentionally empty');
}
if (value == null) {
console.log('Either undefined or null');
}9. What will be the output of: '5' + 3 - 2
Difficulty: HardType: MCQTopic: JS Operators
The answer is 51. Let me break it down step by step.
First step: The string 5 plus the number 3. The plus operator sees a string, so it performs string concatenation. This gives us the string 53.
Second step: The string 53 minus the number 2. The minus operator only works with numbers, so it converts the string 53 to number 53, then subtracts 2. This gives us 51.
Key rules:
The plus operator with any string performs concatenation.
All other operators like minus, multiply, and divide perform numeric conversion.
Think of plus as the special operator:
Plus adapts. If it sees a string, it becomes a string operator.
Minus, multiply, and divide are strict. They only work with numbers and force conversion.
Correct Answer: 51
Example Code
// Step by step evaluation
console.log('5' + 3 - 2);
// Step 1: '5' + 3 = '53' (string concatenation)
// Step 2: '53' - 2 = 51 (numeric conversion then subtraction)
// Result: 51
// More examples
console.log('5' + 3); // '53' (string)
console.log('5' - 3); // 2 (number)
console.log('5' * 3); // 15 (number)
console.log('5' / 2); // 2.5 (number)
// Order matters
console.log(3 + 5 + '7'); // '87' (8 + '7')
console.log('7' + 3 + 5); // '735' (all strings)
// Confusing examples
console.log('10' - 5 + 3); // 8
console.log('10' + 5 - 3); // 102 (string '105' - 3)
console.log(5 + 3 + '2'); // '82'
console.log('2' + 3 + 5); // '235'
// Force numeric conversion
console.log(+'5' + 3); // 8 (unary plus converts to number)
console.log(Number('5') + 3); // 8 (explicit conversion)10. How many falsy values are there in JavaScript?
Difficulty: MediumType: MCQTopic: JS Operators
JavaScript has exactly 8 falsy values. Everything else is truthy.
The 8 falsy values:
False, the boolean false.
Zero, the number zero.
Negative zero, which is minus zero.
BigInt zero, which is 0n.
Empty string with no characters.
Null.
Undefined.
NaN, which means Not a Number.
Memory trick: F O Z E N U N, sounds like frozen.
False, zero including 0n and negative 0, Empty string, Null, Undefined, NaN.
Common mistakes:
Empty arrays and empty objects are truthy, not falsy.
The string zero is truthy.
The string false is truthy.
Anything not in the list of 8 is truthy.
Correct Answer: 8
Example Code
// All 8 falsy values
if (false) { } // falsy
if (0) { } // falsy
if (-0) { } // falsy
if (0n) { } // falsy (BigInt zero)
if ('') { } // falsy (empty string)
if (null) { } // falsy
if (undefined) { } // falsy
if (NaN) { } // falsy
// Common truthy values (SURPRISING!)
if ('0') { } // truthy! (string with zero)
if ('false') { } // truthy! (string false)
if ([]) { } // truthy! (empty array)
if ({}) { } // truthy! (empty object)
if (function(){}) { } // truthy! (functions)
// Practical usage
let name = '';
if (name) {
console.log('Has name');
} else {
console.log('No name'); // This runs (empty string is falsy)
}
// Convert to boolean
console.log(Boolean(0)); // false
console.log(Boolean('hello')); // true
console.log(Boolean([])); // true
// Double NOT operator
console.log(!!0); // false
console.log(!!'hello'); // true
console.log(!![]); // true11. Explain the differences between var, let, and const with examples. When should you use each?
Difficulty: MediumType: SubjectiveTopic: Scope Hoisting
Variable declared with var Has function scope. Accessible anywhere within the function, even outside blocks. This causes variable leaking.
Is hoisted and initialized as undefined. You can access it before declaration.
Can be redeclared in the same scope without errors.
Variable declared with let:
Has block scope. Only exists within curly braces. This prevents variable leaking.
Is hoisted but not initialized. Stays in Temporal Dead Zone. Accessing before declaration throws ReferenceError.
Cannot be redeclared in the same scope. Prevents accidental overwrites.
Variable declared with const:
Has block scope, just like let.
Is hoisted but not initialized, stays in Temporal Dead Zone.
Cannot be redeclared or reassigned. Value is locked.
Important: You can modify properties of const objects or add elements to const arrays. Const prevents reassignment, not mutation.
When to use each:
Use const by default for 90 percent of variables. This includes objects and arrays whose contents you will modify.
Use let when the value will change, like loop counters or calculations that update.
Never use var in modern JavaScript. It causes problems and is banned in many companies.
Example Code
// VAR - function scoped (old way, avoid this)
function testVar() {
console.log(x); // undefined (hoisted)
var x = 10;
if (true) {
var x = 20; // Same variable! (overwrites)
}
console.log(x); // 20 (changed everywhere)
}
// LET - block scoped (modern way)
function testLet() {
// console.log(y); // ReferenceError (TDZ)
let y = 10;
if (true) {
let y = 20; // Different variable (block scoped)
console.log(y); // 20
}
console.log(y); // 10 (unchanged)
}
// CONST - cannot reassign
const PI = 3.14;
// PI = 3.15; // Error! Cannot reassign
// But can modify object contents
const person = { name: 'John' };
person.name = 'Jane'; // OK! Modifying property
person.age = 30; // OK! Adding property
// person = {}; // Error! Cannot reassign
const numbers = [1, 2, 3];
numbers.push(4); // OK! Modifying array
// numbers = []; // Error! Cannot reassign
// WHEN TO USE WHAT
// Use const (default choice)
const API_URL = 'https://api.example.com';
const users = [];
const config = { timeout: 5000 };
// Use let (when value changes)
let score = 0;
score += 10;
score -= 5;
for (let i = 0; i < 5; i++) {
console.log(i);
}
// NEVER use var (causes problems)
if (true) {
var leaked = 'I escaped!';
}
console.log(leaked); // Works but BAD
// Loop problem with var
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Prints: 3, 3, 3 (all share same i)
// Fixed with let
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Prints: 0, 1, 2 (each has own i)12. What is hoisting in JavaScript? Explain how it works for variables and functions with examples.
Difficulty: MediumType: SubjectiveTopic: Scope Hoisting
Hoisting is JavaScript’s behavior of moving declarations to the top of their scope during compilation.
Function declarations are hoisted with their definitions, so you can call them before they appear.
var declarations are hoisted and initialized to undefined.
let and const are hoisted but not initialized, so accessing before declaration causes a ReferenceError (temporal dead zone).
Example Code
// VAR HOISTING
console.log(name); // undefined (not error)
var name = 'John';
console.log(name); // 'John'
// What JavaScript actually does:
var name; // Declaration moved to top
console.log(name); // undefined
name = 'John'; // Assignment stays here
console.log(name); // 'John'
// LET/CONST HOISTING (Temporal Dead Zone)
// console.log(age); // ReferenceError!
let age = 25;
// console.log(PI); // ReferenceError!
const PI = 3.14;
// FUNCTION DECLARATION HOISTING
greet(); // 'Hello!' - Works before definition!
function greet() {
console.log('Hello!');
}
// FUNCTION EXPRESSION - NOT HOISTED
// sayHi(); // TypeError: sayHi is not a function
var sayHi = function() {
console.log('Hi!');
};
// What happens:
var sayHi; // Only variable hoisted as undefined
// sayHi(); // TypeError: undefined is not a function
sayHi = function() { // Function assigned here
console.log('Hi!');
};
// ARROW FUNCTION - NOT HOISTED
// welcome(); // ReferenceError
const welcome = () => {
console.log('Welcome!');
};
// TRICKY EXAMPLE
var x = 1;
function test() {
console.log(x); // undefined (not 1!)
var x = 2;
console.log(x); // 2
}
// Why undefined?
function test() {
var x; // Local x hoisted, shadows global x
console.log(x); // undefined
x = 2;
console.log(x); // 2
}13. List all JavaScript data types. Explain the difference between primitive and reference types.
Difficulty: EasyType: SubjectiveTopic: Data Types
In JavaScript we have two main categories of data types: primitive and reference types.
The 7 primitive types are: string, number, bigint, boolean, undefined, null, and symbol.
Reference types are basically objects, which include regular objects, arrays, and functions.
Primitives are immutable and stored by value, so when you copy them you get a separate value.
Reference types are mutable and stored by reference, so when you copy them you’re copying a reference to the same object in memory.
Example Code
// 7 PRIMITIVE TYPES
// 1. String
let name = 'John';
let message = "Hello";
let template = `Hi ${name}`;
// 2. Number
let age = 25;
let price = 19.99;
let infinity = 1 / 0; // Infinity
let notNumber = 'text' / 2; // NaN
// 3. BigInt
let huge = 9007199254740991n;
// 4. Boolean
let isActive = true;
// 5. undefined
let notAssigned;
console.log(notAssigned); // undefined
// 6. null
let empty = null;
// 7. Symbol
let id1 = Symbol('id');
let id2 = Symbol('id');
console.log(id1 === id2); // false (always unique)
// REFERENCE TYPES
let person = { name: 'John', age: 30 };
let numbers = [1, 2, 3];
function greet() { }
// PRIMITIVE vs REFERENCE BEHAVIOR
// Primitives - copy by value
let a = 5;
let b = a; // Copy of value
b = 10;
console.log(a); // 5 (unchanged)
console.log(b); // 10
// Reference - copy by reference
let obj1 = { value: 5 };
let obj2 = obj1; // Reference to same object
obj2.value = 10;
console.log(obj1.value); // 10 (changed!)
console.log(obj2.value); // 10
// Primitive comparison
let str1 = 'hello';
let str2 = 'hello';
console.log(str1 === str2); // true (same value)
// Reference comparison
let arr1 = [1, 2, 3];
let arr2 = [1, 2, 3];
console.log(arr1 === arr2); // false (different objects)
// Primitive immutability
let text = 'hello';
text[0] = 'H'; // Ignored
console.log(text); // 'hello' (unchanged)
// Reference mutability
let list = [1, 2, 3];
list[0] = 10; // Changes the array
list.push(4); // Adds to array
console.log(list); // [10, 2, 3, 4]14. Explain the different types of scope in JavaScript: global, function, and block scope.
Difficulty: MediumType: SubjectiveTopic: Scope Hoisting
Scope determines where variables are accessible in your code.
Global scope:
Variables declared outside any function or block. Accessible everywhere in your code. In browsers, global variables become properties of window object. Be careful with globals because they can be modified from anywhere.
Function scope:
Variables declared inside a function. Only accessible within that function. This includes var, let, and const. Function scope creates privacy and prevents variables from leaking out.
Block scope:
Variables declared with let or const inside curly braces. Only accessible within that block. Blocks include if statements, for loops, while loops, or any curly braces. Block scope was introduced in ES6. Variables declared with var ignore block scope and use function scope instead.
Scope chain:
JavaScript uses a scope chain to look up variables. It starts in current scope. If not found, moves to parent scope. Continues until reaching global scope. If still not found, you get ReferenceError. Inner functions can access outer variables, but outer functions cannot access inner variables.
Lexical scoping:
JavaScript uses lexical scoping, also called static scoping. Scope is determined by where code is written, not where it is called. Inner functions remember their outer scope even after outer function finishes. This is the basis for closures.
Best practices:
Minimize global variables. Declare variables in smallest scope needed. Use let and const for block scope. Never rely on variable leaking from blocks.
Example Code
// GLOBAL SCOPE
let globalVar = 'I am global';
var alsoGlobal = 'Me too';
function test() {
console.log(globalVar); // Accessible everywhere
}
console.log(globalVar); // Works
// Global variables on window (browser)
var x = 10;
console.log(window.x); // 10
let y = 20;
console.log(window.y); // undefined
// FUNCTION SCOPE
function calculate() {
let result = 10 * 5;
var temp = 100;
console.log(result); // 50
console.log(temp); // 100
}
// console.log(result); // Error!
// console.log(temp); // Error!
// BLOCK SCOPE
if (true) {
let blockVar = 'I am block scoped';
const alsoBlock = 'Me too';
var notBlock = 'I leak out!';
console.log(blockVar); // Works
}
// console.log(blockVar); // Error!
console.log(notBlock); // Works! (BAD)
// Block scope in loops
for (let i = 0; i < 3; i++) {
console.log(i);
}
// console.log(i); // Error!
for (var j = 0; j < 3; j++) {
// j leaks out
}
console.log(j); // 3 (leaked)
// SCOPE CHAIN
let outer = 'outer';
function first() {
let middle = 'middle';
function second() {
let inner = 'inner';
console.log(inner); // Accessible
console.log(middle); // Accessible
console.log(outer); // Accessible
}
second();
// console.log(inner); // Error!
}
first();15. Can you modify properties of a const object or elements of a const array? Explain why or why not.
Difficulty: MediumType: SubjectiveTopic: Scope Hoisting
Yes, you can modify the properties of a const object and the elements of a const array.
In JavaScript, const only prevents reassignment of the variable, not changes to the object or array it references.
So I can update properties, push or pop from an array, but I can’t make that const variable point to a completely new object or array.
If I really want to make an object immutable, I’d use something like Object.freeze() on it.
Example Code
// MODIFYING CONST OBJECTS
const person = {
name: 'John',
age: 30
};
// These all work - modifying properties
person.name = 'Jane'; // Change property
person.city = 'New York'; // Add property
delete person.age; // Delete property
console.log(person); // { name: 'Jane', city: 'New York' }
// This fails - reassigning variable
// person = { name: 'Bob' }; // TypeError
// MODIFYING CONST ARRAYS
const numbers = [1, 2, 3];
// These all work - modifying contents
numbers.push(4); // Add element
numbers.pop(); // Remove element
numbers[0] = 10; // Change element
console.log(numbers); // Modified array
// This fails - reassigning variable
// numbers = [5, 6, 7]; // TypeError
// WHY IT WORKS
const obj = { x: 1 };
// obj points to memory location
obj.x = 2; // OK - changing contents
obj.y = 3; // OK - adding to contents
// obj = {}; // Error - trying to point elsewhere
// MAKING TRULY IMMUTABLE
const frozen = Object.freeze({ x: 1, y: 2 });
frozen.x = 10; // Ignored
frozen.z = 3; // Cannot add
delete frozen.y; // Cannot delete
console.log(frozen); // { x: 1, y: 2 } (unchanged)
// SHALLOW FREEZE (only top level)
const nested = Object.freeze({
name: 'John',
address: { city: 'NYC' }
});
// nested.name = 'Jane'; // Cannot change
nested.address.city = 'LA'; // Can change (nested not frozen)
// PRACTICAL EXAMPLE
const shoppingCart = [];
shoppingCart.push({ item: 'Book', price: 10 });
shoppingCart.push({ item: 'Pen', price: 2 });
// But we don't want to accidentally reassign
// shoppingCart = []; // Error! Protected by const16. What is the result of 10 + '5' in JavaScript?
Difficulty: EasyType: MCQTopic: JS Operators
The result is the string 105 because of type coercion.
When the plus operator is used with a string and a number, JavaScript converts the number to a string and performs concatenation instead of addition.
The number 10 becomes the string 10, then concatenates with the string 5 to produce 105.
Key rule to remember:
The plus operator with any string performs string concatenation. Other arithmetic operators like minus, multiply, and divide perform numeric conversion.
This is a common source of bugs, so always be careful when using plus with mixed types.
Correct Answer: '105'
Example Code
console.log(10 + '5'); // '105' (string concatenation)
console.log(10 - '5'); // 5 (numeric subtraction)
console.log(10 * '5'); // 50 (numeric multiplication)
console.log('10' / '5'); // 2 (numeric division)
// Force numeric addition
console.log(10 + Number('5')); // 15
console.log(10 + (+'5')); // 15 (unary plus)
// Multiple operations
console.log(10 + 5 + '5'); // '155' (15 + '5')
console.log('5' + 10 + 5); // '5105' (all strings)17. What does the expression 0 || 'Hello' || false return?
Difficulty: MediumType: MCQTopic: JS Operators
The result is the string Hello.
The logical OR operator returns the first truthy value it encounters. It evaluates from left to right.
Zero is falsy, so it skips it. Hello is truthy, so it immediately returns Hello without checking false.
This is called short-circuit evaluation. Once a truthy value is found, the rest is not evaluated.
This behavior is useful for setting default values.
If all values were falsy, it would return the last value.
Correct Answer: 'Hello'
Example Code
// OR returns first truthy value
console.log(0 || 'Hello' || false); // 'Hello'
console.log(false || null || 'World'); // 'World'
console.log(false || 0 || ''); // '' (last value)
// Common pattern for defaults
function greet(name) {
name = name || 'Guest';
console.log('Hello ' + name);
}
greet(); // 'Hello Guest'
greet('John'); // 'Hello John'
// AND returns first falsy or last value
console.log(true && 'Hello' && 5); // 5
console.log('Hi' && 0 && 'Bye'); // 0
// Short-circuit example
let count = 0;
console.log(true || count++); // true (count++ never runs)
console.log(count); // 0 (not incremented)18. What is the correct syntax of the ternary operator?
Difficulty: EasyType: MCQTopic: JS Operators
- condition ? valueIfTrue : valueIfFalse
- if condition then value else value
- condition ? value1 ? value2
- condition : value1 : value2
The ternary operator syntax is: condition ? valueIfTrue : valueIfFalse
It is a shorthand for if else statements. The condition is evaluated first. If true, it returns the value after the question mark. If false, it returns the value after the colon.
It is the only JavaScript operator that takes three operands, hence the name ternary.
Use it for simple conditions to make code more concise. Avoid nesting multiple ternary operators as it reduces readability.
Correct Answer: condition ? valueIfTrue : valueIfFalse
Example Code
// Basic ternary
let age = 20;
let status = age >= 18 ? 'Adult' : 'Minor';
console.log(status); // 'Adult'
// Same as if-else
let status2;
if (age >= 18) {
status2 = 'Adult';
} else {
status2 = 'Minor';
}
// In function return
function checkEven(num) {
return num % 2 === 0 ? 'Even' : 'Odd';
}
console.log(checkEven(4)); // 'Even'
console.log(checkEven(7)); // 'Odd'
// Inline usage
let score = 85;
console.log('You ' + (score >= 60 ? 'passed' : 'failed'));
// Avoid deep nesting (hard to read)
let grade = score > 90 ? 'A' : score > 80 ? 'B' : 'C';19. What is the difference between x++ and ++x?
Difficulty: MediumType: MCQTopic: JS Operators
- No difference, both are same
- x++ returns old value then increments, ++x increments then returns new value
- x++ is faster than ++x
- ++x can only be used with numbers
Post increment returns the current value first, then increments the variable.
Pre increment increments the variable first, then returns the new value.
The difference matters when the expression is used in an assignment or comparison.
If you just write the increment on its own line, both behave the same way.
Post increment is more commonly used in loops, but understanding the difference prevents bugs in complex expressions.
Correct Answer: x++ returns old value then increments, ++x increments then returns new value
Example Code
// Post-increment (x++)
let a = 5;
let b = a++; // b gets 5, then a becomes 6
console.log(b); // 5
console.log(a); // 6
// Pre-increment (++x)
let x = 5;
let y = ++x; // x becomes 6, then y gets 6
console.log(y); // 6
console.log(x); // 6
// Common in loops
for (let i = 0; i < 5; i++) { // Post-increment
console.log(i); // 0, 1, 2, 3, 4
}
// In comparisons
let num = 10;
console.log(num++ === 10); // true (compares before increment)
console.log(num); // 11
let num2 = 10;
console.log(++num2 === 10); // false (increments before compare)
console.log(num2); // 1120. What does the nullish coalescing operator (??) do?
Difficulty: MediumType: MCQTopic: Modern Syntax
- Returns right side if left is null or undefined only
- Returns right side if left is any falsy value
- Checks if both sides are null
- Converts null to undefined
The nullish coalescing operator returns the right operand when the left operand is null or undefined, and returns the left operand otherwise.
Unlike the OR operator which checks for any falsy value, nullish coalescing only checks for null and undefined.
This is useful when zero or empty string are valid values you want to keep.
It was introduced in ES2020 to provide a more precise way to set default values.
Correct Answer: Returns right side if left is null or undefined only
Example Code
// Nullish coalescing - only null/undefined
console.log(null ?? 'default'); // 'default'
console.log(undefined ?? 'default'); // 'default'
console.log(0 ?? 'default'); // 0 (keeps 0)
console.log('' ?? 'default'); // '' (keeps empty string)
console.log(false ?? 'default'); // false (keeps false)
// Compare with OR operator
console.log(0 || 'default'); // 'default' (treats 0 as falsy)
console.log('' || 'default'); // 'default' (treats '' as falsy)
// Practical use
function setPort(port) {
return port ?? 3000; // Keeps 0 as valid port
}
console.log(setPort(8080)); // 8080
console.log(setPort(0)); // 0 (not 3000!)
console.log(setPort(null)); // 3000
// User settings
let volume = settings.volume ?? 50; // 0 is valid volume21. What does user?.address?.city return if address is undefined?
Difficulty: MediumType: MCQTopic: Modern Syntax
- undefined
- null
- Error
- empty string
Optional chaining returns undefined if any part of the chain is null or undefined, without throwing an error.
When address is undefined, the expression stops evaluating and immediately returns undefined instead of trying to access city on undefined which would cause a TypeError.
This makes code safer and cleaner by eliminating the need for multiple existence checks.
It was introduced in ES2020 and works with object properties, array elements, and function calls.
Correct Answer: undefined
Example Code
// Optional chaining
let user = { name: 'John' };
console.log(user?.address?.city); // undefined (no error)
// Without optional chaining
// console.log(user.address.city); // TypeError!
// Traditional way
let city = user && user.address && user.address.city;
// With arrays
let arr = null;
console.log(arr?.[0]); // undefined
// With functions
let obj = {};
console.log(obj.method?.()); // undefined (no error)
// Real example
let data = { users: [{ name: 'Alice' }] };
console.log(data?.users?.[0]?.name); // 'Alice'
console.log(data?.users?.[5]?.name); // undefined
// API response
let response = await fetch('/api/user');
let userName = response?.data?.user?.name ?? 'Guest';22. Can you call a function declaration before it is defined in the code?
Difficulty: EasyType: MCQTopic: JS Functions
- No, it will throw an error
- Yes, due to hoisting
- Only in strict mode
- Only if it has no parameters
Yes, you can call a function declaration before it is defined because of hoisting.
JavaScript moves the entire function declaration including its body to the top of the scope during compilation.
This is different from function expressions and arrow functions which are not hoisted in the same way.
Function declarations are fully available throughout their scope. This behavior is useful but can lead to confusion, so many developers prefer to define functions before using them for better readability.
Correct Answer: Yes, due to hoisting
Example Code
// Function declaration - can call before definition
greet(); // Works! Prints 'Hello'
function greet() {
console.log('Hello');
}
// Function expression - cannot call before definition
// sayBye(); // TypeError: sayBye is not a function
let sayBye = function() {
console.log('Bye');
};
// Arrow function - cannot call before definition
// welcome(); // ReferenceError
const welcome = () => {
console.log('Welcome');
};
// Why function declaration works
function calculate(a, b) {
return a + b;
}
// JavaScript sees it as:
// Function moved to top, then code runs
console.log(calculate(5, 3)); // 823. What is the main difference between arrow functions and regular functions?
Difficulty: MediumType: MCQTopic: Arrow Functions
- Arrow functions are faster
- Arrow functions don't have their own this binding
- Arrow functions can only have one parameter
- Arrow functions cannot return values
The main difference is that arrow functions do not have their own this binding.
They inherit this from the parent scope where they are defined, called lexical this.
Regular functions have their own this which depends on how the function is called.
Arrow functions also do not have arguments object, cannot be used as constructors with new, and do not have prototype property.
They provide shorter syntax and are commonly used for callbacks and functional programming patterns.
Correct Answer: Arrow functions don't have their own this binding
Example Code
// Arrow function - lexical this
const obj = {
name: 'Object',
regularFunc: function() {
console.log(this.name); // 'Object'
},
arrowFunc: () => {
console.log(this.name); // undefined (uses parent scope this)
}
};
obj.regularFunc(); // 'Object'
obj.arrowFunc(); // undefined
// Arrow syntax variations
const add1 = (a, b) => { return a + b; };
const add2 = (a, b) => a + b; // Implicit return
const square = x => x * x; // Single param, no parentheses
const greet = () => 'Hello'; // No params
// Cannot use as constructor
const Person = (name) => { this.name = name; };
// const p = new Person('John'); // TypeError
// Use cases
const numbers = [1, 2, 3];
const doubled = numbers.map(n => n * 2); // Clean callback
console.log(doubled); // [2, 4, 6]24. What happens if you call a function with fewer arguments than parameters?
Difficulty: EasyType: MCQTopic: JS Functions
- Error is thrown
- Missing parameters are undefined
- Missing parameters are null
- Function doesn't execute
If you call a function with fewer arguments than parameters, the missing parameters are automatically set to undefined.
JavaScript does not enforce the number of arguments and allows you to pass more or fewer arguments than the function expects.
This flexibility can be useful but also requires defensive coding to handle missing arguments.
You can use default parameters introduced in ES6 to provide fallback values for missing arguments.
Correct Answer: Missing parameters are undefined
Example Code
function greet(firstName, lastName) {
console.log(firstName); // 'John'
console.log(lastName); // undefined
}
greet('John'); // Called with only 1 argument
// Default parameters (ES6)
function sayHello(name = 'Guest') {
console.log('Hello ' + name);
}
sayHello(); // 'Hello Guest'
sayHello('John'); // 'Hello John'
// Old way to handle defaults
function oldWay(name) {
name = name || 'Guest';
console.log('Hello ' + name);
}
// Multiple defaults
function multiply(a, b = 1) {
return a * b;
}
console.log(multiply(5)); // 5 (5 * 1)
console.log(multiply(5, 2)); // 10
// Undefined triggers default
function test(x = 10) {
console.log(x);
}
test(undefined); // 10 (default used)
test(null); // null (default NOT used)25. What does a function return if there is no return statement?
Difficulty: EasyType: MCQTopic: JS Functions
A function without a return statement returns undefined by default.
This also happens if you use return with no value, or if execution reaches the end of the function without hitting a return.
Every function in JavaScript returns something, and undefined is the default return value.
Understanding this behavior is important for debugging and knowing when to explicitly return values.
Correct Answer: undefined
Example Code
// No return statement
function noReturn() {
let x = 5;
}
console.log(noReturn()); // undefined
// Empty return
function emptyReturn() {
return;
}
console.log(emptyReturn()); // undefined
// Return with value
function withReturn() {
return 42;
}
console.log(withReturn()); // 42
// Early return
function checkAge(age) {
if (age < 18) {
return 'Too young';
}
return 'Welcome';
}
// Common mistake
function calculate() {
let result = 10 * 5;
// Forgot to return!
}
console.log(calculate()); // undefined26. Explain type coercion in JavaScript with examples. How do different operators handle it?
Difficulty: MediumType: SubjectiveTopic: JS Operators
Type coercion is JavaScript's automatic conversion of values from one data type to another. It happens when operators or comparisons are used with mixed types.
The plus operator performs string concatenation if either operand is a string, converting the other to string.
Other arithmetic operators like minus, multiply, divide always convert operands to numbers.
Comparison operators like double equals perform type coercion before comparing, while triple equals does not.
The logical operators OR and AND work with truthy and falsy values, using implicit boolean conversion.
Understanding coercion is crucial because it can cause unexpected bugs.
To avoid issues:
Use explicit conversion with Number, String, or Boolean functions. Always use triple equals for comparisons. Be careful with the plus operator when dealing with mixed types.
Example Code
// String coercion with +
console.log(5 + '5'); // '55' (number to string)
console.log('Hello' + true); // 'Hellotrue'
// Number coercion with other operators
console.log('10' - 5); // 5 (string to number)
console.log('10' * '2'); // 20
console.log('10' / '2'); // 5
console.log('5' - true); // 4 (true becomes 1)
// Boolean coercion
console.log(!'hello'); // false (string is truthy)
console.log(!0); // true (0 is falsy)
// Comparison coercion
console.log(5 == '5'); // true (coerces types)
console.log(5 === '5'); // false (no coercion)
console.log(null == undefined); // true (special case)
// Explicit conversion
console.log(Number('42')); // 42
console.log(String(42)); // '42'
console.log(Boolean(0)); // false
// Tricky examples
console.log('5' + 3 - 2); // 51 ('53' - 2)
console.log([] + []); // '' (empty string)
console.log([] + {}); // '[object Object]'
console.log({} + []); // '[object Object]'27. What are the different ways to define functions in JavaScript? Compare function declarations, expressions, and arrow functions.
Difficulty: MediumType: SubjectiveTopic: JS Functions
JavaScript has three main ways to define functions.
Function declaration uses the function keyword followed by a name. It creates a named function that is fully hoisted and can be called before its definition. Good for top level functions and when you need hoisting.
Function expression assigns a function to a variable. It can be named or anonymous. It follows variable hoisting rules so cannot be called before assignment. Useful for conditional function creation or passing functions as values.
Arrow function introduced in ES6 provides shorter syntax. It does not have its own this binding and inherits from parent scope. Cannot be used as constructor with new. Has implicit return for single expressions. Ideal for callbacks, array methods, and when you want lexical this binding.
When to use what:
Use function declarations for main functions that need hoisting. Use function expressions for functions as values or conditional creation. Use arrow functions for callbacks and when you need lexical this. Choose based on your needs for hoisting, this binding, and syntax preference.
Example Code
// Function Declaration
function greet(name) {
return 'Hello ' + name;
}
// Hoisted - can call before definition
// Has own 'this'
// Can be used as constructor
// Function Expression
const sayHi = function(name) {
return 'Hi ' + name;
};
// Not hoisted - cannot call before
// Has own 'this'
// Anonymous or named
// Arrow Function
const welcome = (name) => {
return 'Welcome ' + name;
};
// Shorter version
const greetShort = name => 'Hello ' + name;
// Not hoisted
// Lexical 'this' from parent
// Cannot be constructor
// Implicit return for single expression
// Use cases
// Callbacks with arrow function
[1, 2, 3].map(n => n * 2);
// Method with regular function
const obj = {
name: 'Object',
greet: function() {
console.log(this.name); // Works
}
};
// Event handler needs this
button.addEventListener('click', function() {
console.log(this); // button element
});28. Explain default parameters in JavaScript functions. How do they work with undefined and null?
Difficulty: EasyType: SubjectiveTopic: JS Functions
Default parameters allow you to specify default values for function parameters if no argument is provided or if undefined is passed.
Introduced in ES6, they provide cleaner syntax than the old pattern of using logical OR or checking for undefined.
Default parameters are evaluated at call time, so you can use expressions or even call other functions as defaults.
Parameters with defaults must come after required parameters.
If you pass undefined explicitly, the default value is used. But passing null does not trigger the default because null is a valid value.
Default parameters make functions more robust by handling missing arguments gracefully without additional code inside the function body.
Example Code
// Basic default parameters
function greet(name = 'Guest', greeting = 'Hello') {
return greeting + ' ' + name;
}
console.log(greet()); // 'Hello Guest'
console.log(greet('John')); // 'Hello John'
console.log(greet('John', 'Hi')); // 'Hi John'
// undefined triggers default
console.log(greet(undefined, 'Hey')); // 'Hey Guest'
// null does not trigger default
console.log(greet(null)); // 'Hello null'
// Expression as default
function multiply(a, b = a * 2) {
return a * b;
}
console.log(multiply(5)); // 25 (5 * 10)
console.log(multiply(5, 3)); // 15
// Function call as default
function getDefault() {
return 'Default Value';
}
function demo(value = getDefault()) {
return value;
}
console.log(demo()); // 'Default Value'
console.log(demo('Hi')); // 'Hi'
// Multiple defaults
function createUser(name = 'Anonymous', age = 18, role = 'user') {
return { name, age, role };
}
console.log(createUser()); // {name: 'Anonymous', age: 18, role: 'user'}29. What are rest parameters? How do they differ from the arguments object?
Difficulty: MediumType: SubjectiveTopic: Modern Syntax
Rest parameters use three dots before the parameter name to collect all remaining arguments into a real array.
Unlike the arguments object which is array-like but not a real array, rest parameters are actual arrays with all array methods available.
Rest parameters must be the last parameter in the function signature.
They provide a cleaner way to handle variable number of arguments compared to the arguments object.
Arrow functions do not have arguments object, so rest parameters are the only way to access all arguments in arrow functions.
Rest parameters make code more readable and are preferred in modern JavaScript over the arguments object.
Example Code
// Rest parameters - real array
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
// Can use array methods
function getMax(...nums) {
return Math.max(...nums);
}
console.log(getMax(5, 2, 9, 1)); // 9
// Mixed parameters
function introduce(greeting, ...names) {
return greeting + ' ' + names.join(' and ');
}
console.log(introduce('Hello', 'John', 'Jane')); // 'Hello John and Jane'
// Arrow function with rest
const multiply = (...nums) => {
return nums.reduce((product, num) => product * num, 1);
};
console.log(multiply(2, 3, 4)); // 24
// Arguments object (old way)
function oldSum() {
let total = 0;
for (let i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}
// arguments is array-like, not real array
// Does not work in arrow functions
// Rest vs arguments
function compare(...args) {
console.log(Array.isArray(args)); // true
console.log(args.map(x => x * 2)); // Works!
}30. Explain the spread operator. What are its common use cases in JavaScript?
Difficulty: MediumType: SubjectiveTopic: Modern Syntax
The spread operator uses three dots to expand an iterable like array or object into individual elements.
For arrays, it expands elements for function arguments, copying arrays, or combining arrays.
For objects, it copies properties and can be used for merging objects, with later properties overwriting earlier ones.
Spread creates shallow copies, meaning nested objects or arrays are still referenced.
It provides cleaner syntax compared to older methods like concat or Object assign.
Common use cases:
Passing array as function arguments. Cloning arrays or objects. Merging multiple arrays or objects. Converting iterables like strings into arrays.
Understanding spread operator is essential for modern JavaScript as it is widely used in React and other frameworks.
Example Code
// Array spreading
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2]; // [1,2,3,4,5,6]
// Copy array
const original = [1, 2, 3];
const copy = [...original];
copy.push(4); // Does not affect original
// Function arguments
const numbers = [5, 10, 15];
console.log(Math.max(...numbers)); // 15
// Same as: Math.max(5, 10, 15)
// Object spreading
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
const merged = { ...obj1, ...obj2 }; // {a:1, b:2, c:3, d:4}
// Override properties
const user = { name: 'John', age: 30 };
const updated = { ...user, age: 31 }; // age overwritten
// String to array
const str = 'hello';
const chars = [...str]; // ['h','e','l','l','o']
// Add elements while spreading
const nums = [2, 3, 4];
const extended = [1, ...nums, 5]; // [1,2,3,4,5]
// Shallow copy warning
const nested = { x: 1, y: { z: 2 } };
const shallowCopy = { ...nested };
shallowCopy.y.z = 3; // Also changes nested.y.z!31. How do you create an array in JavaScript?
Difficulty: EasyType: MCQTopic: JS Arrays
- array = (1, 2, 3)
- array = [1, 2, 3]
- array = {1, 2, 3}
- array = <1, 2, 3>
Arrays are created using square brackets with elements separated by commas.
You can also create arrays using the Array constructor, but square bracket notation is more common and preferred.
Arrays can hold any data type: numbers, strings, objects, other arrays, or mixed types.
Arrays in JavaScript are zero-indexed, meaning the first element is at position 0.
Arrays are dynamic, so you can add or remove elements after creation.
Correct Answer: array = [1, 2, 3]
Example Code
// Array literal (most common)
const numbers = [1, 2, 3, 4, 5];
const fruits = ['apple', 'banana', 'orange'];
// Mixed types
const mixed = [1, 'hello', true, null, {name: 'John'}];
// Empty array
const empty = [];
// Using Array constructor (less common)
const arr1 = new Array(1, 2, 3);
const arr2 = new Array(5); // Creates array with 5 empty slots
// Nested arrays
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
// Accessing elements (zero-indexed)
console.log(numbers[0]); // 1
console.log(fruits[1]); // 'banana'
console.log(numbers[numbers.length - 1]); // 5 (last element)32. Which method adds an element to the end of an array?
Difficulty: EasyType: MCQTopic: JS Arrays
- array.add(element)
- array.push(element)
- array.append(element)
- array.insert(element)
The push method adds one or more elements to the end of an array and returns the new length.
Push modifies the original array directly. It does not create a new array.
You can push multiple elements at once by passing multiple arguments.
The opposite of push is pop, which removes the last element.
Push is one of the most commonly used array methods in JavaScript.
Correct Answer: array.push(element)
Example Code
const fruits = ['apple', 'banana'];
// Push single element
fruits.push('orange');
console.log(fruits); // ['apple', 'banana', 'orange']
// Push multiple elements
fruits.push('grape', 'mango');
console.log(fruits); // ['apple', 'banana', 'orange', 'grape', 'mango']
// Push returns new length
const newLength = fruits.push('kiwi');
console.log(newLength); // 6
// Other array methods
const numbers = [1, 2, 3];
numbers.pop(); // Removes last element (3)
numbers.unshift(0); // Adds to beginning
numbers.shift(); // Removes first element
console.log(numbers); // [1, 2]33. What does array.length return?
Difficulty: EasyType: MCQTopic: JS Arrays
- The last element of the array
- The number of elements in the array
- The index of the last element
- The array itself
The length property returns the number of elements in an array.
It is always one more than the highest index because arrays are zero-indexed.
You can also set the length property to truncate or extend an array.
Setting length to 0 is a quick way to empty an array.
The length property is automatically updated when you add or remove elements.
Correct Answer: The number of elements in the array
Example Code
const numbers = [10, 20, 30, 40, 50];
// Get length
console.log(numbers.length); // 5
// Access last element using length
console.log(numbers[numbers.length - 1]); // 50
// Set length to truncate
numbers.length = 3;
console.log(numbers); // [10, 20, 30]
// Set length to 0 (empty array)
numbers.length = 0;
console.log(numbers); // []
// Empty array check
const arr = [];
if (arr.length === 0) {
console.log('Array is empty');
}
// Length is always 1 more than highest index
const sparse = [1, 2, 3];
sparse[10] = 100;
console.log(sparse.length); // 11 (not 4!)34. What is the correct syntax for a for loop?
Difficulty: EasyType: MCQTopic: JS Loops
- for (i = 0; i < 5; i++)
- for i = 0 to 5
- for (let i = 0; i < 5; i++)
- loop (i = 0; i < 5; i++)
The correct syntax has three parts inside parentheses: initialization, condition, and increment.
Initialization runs once at the start. Condition is checked before each iteration. Increment runs after each iteration.
Use let instead of var for the loop variable due to block scoping.
The code block to execute goes inside curly braces after the parentheses.
For loops are ideal when you know how many times to iterate.
Correct Answer: for (let i = 0; i < 5; i++)
Example Code
// Basic for loop
for (let i = 0; i < 5; i++) {
console.log(i); // 0, 1, 2, 3, 4
}
// Loop through array
const fruits = ['apple', 'banana', 'cherry'];
for (let i = 0; i < fruits.length; i++) {
console.log(fruits[i]);
}
// Reverse loop
for (let i = 5; i > 0; i--) {
console.log(i); // 5, 4, 3, 2, 1
}
// Step by 2
for (let i = 0; i < 10; i += 2) {
console.log(i); // 0, 2, 4, 6, 8
}
// Multiple variables
for (let i = 0, j = 10; i < 5; i++, j--) {
console.log(i, j); // 0 10, 1 9, 2 8, 3 7, 4 6
}
// Nested loops
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
console.log(i, j);
}
}35. What does the forEach method return?
Difficulty: MediumType: MCQTopic: JS Arrays
- A new array
- undefined
- The original array
- The last element
The forEach method always returns undefined. It does not return a new array.
ForEach is used for its side effects, like logging or updating DOM elements, not for transforming arrays.
It executes a callback function for each element but does not create or return anything.
If you need to transform an array, use map instead.
You cannot break or continue out of forEach. Use a regular for loop if you need early exit.
Correct Answer: undefined
Example Code
const numbers = [1, 2, 3];
// forEach returns undefined
const result = numbers.forEach(num => num * 2);
console.log(result); // undefined
// Used for side effects
numbers.forEach(num => {
console.log(num); // Just logging
});
// Accessing index and array
fruits.forEach((fruit, index, array) => {
console.log(index, fruit, array.length);
});
// Compare with map (returns new array)
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6]
// Cannot break out of forEach
numbers.forEach(num => {
// if (num === 2) break; // SyntaxError!
console.log(num);
});
// Use for...of if you need break
for (const num of numbers) {
if (num === 2) break; // Works!
console.log(num);
}36. What does the map method do?
Difficulty: MediumType: MCQTopic: JS Arrays
- Filters elements from an array
- Creates a new array by transforming each element
- Finds a single element in an array
- Sorts the array
The map method creates a new array by applying a function to each element of the original array.
It returns a new array with the same length as the original. The original array remains unchanged.
Map is used for transforming data, like extracting properties, formatting values, or performing calculations.
The callback function receives the current element, index, and the whole array.
Map is one of the most important array methods in functional programming.
Correct Answer: Creates a new array by transforming each element
Example Code
const numbers = [1, 2, 3, 4, 5];
// Transform each element
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
console.log(numbers); // [1, 2, 3, 4, 5] (unchanged)
// Extract property from objects
const users = [
{ name: 'John', age: 25 },
{ name: 'Jane', age: 30 },
{ name: 'Bob', age: 35 }
];
const names = users.map(user => user.name);
console.log(names); // ['John', 'Jane', 'Bob']
// Using index
const indexed = numbers.map((num, index) => num * index);
console.log(indexed); // [0, 2, 6, 12, 20]
// Convert to strings
const strings = numbers.map(num => String(num));
console.log(strings); // ['1', '2', '3', '4', '5']
// Complex transformation
const formatted = users.map(user => ({
fullName: user.name.toUpperCase(),
isAdult: user.age >= 18
}));37. What does the filter method return?
Difficulty: MediumType: MCQTopic: JS Arrays
- A single element that matches the condition
- A new array with elements that pass the test
- True or false
- The index of matching elements
The filter method creates a new array containing only elements that pass a test function.
It returns a new array that may be shorter than the original. If no elements pass, it returns an empty array.
The test function should return true to keep the element, false to exclude it.
Filter does not modify the original array.
It is commonly used to remove unwanted elements or select specific items.
Correct Answer: A new array with elements that pass the test
Example Code
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Filter even numbers
const evens = numbers.filter(num => num % 2 === 0);
console.log(evens); // [2, 4, 6, 8, 10]
// Filter by condition
const greaterThanFive = numbers.filter(num => num > 5);
console.log(greaterThanFive); // [6, 7, 8, 9, 10]
// Filter objects
const users = [
{ name: 'John', age: 17 },
{ name: 'Jane', age: 25 },
{ name: 'Bob', age: 30 }
];
const adults = users.filter(user => user.age >= 18);
console.log(adults); // [{name: 'Jane', age: 25}, {name: 'Bob', age: 30}]
// Remove falsy values
const mixed = [0, 1, false, 2, '', 3, null, 4, undefined, 5];
const truthy = mixed.filter(Boolean);
console.log(truthy); // [1, 2, 3, 4, 5]
// No matches returns empty array
const none = numbers.filter(num => num > 100);
console.log(none); // []38. What does the reduce method do?
Difficulty: HardType: MCQTopic: JS Arrays
- Reduces array size by removing elements
- Accumulates array elements into a single value
- Filters and maps at the same time
- Sorts array in descending order
The reduce method executes a reducer function on each element, accumulating the results into a single value.
It takes a callback function with an accumulator and current value. The accumulator stores the accumulated result.
You can provide an initial value as the second argument. If not provided, the first array element is used.
Reduce is used for summing, finding max or min, flattening arrays, grouping data, or any operation that combines all elements.
It is one of the most powerful but also most complex array methods.
Correct Answer: Accumulates array elements into a single value
Example Code
const numbers = [1, 2, 3, 4, 5];
// Sum all numbers
const sum = numbers.reduce((acc, num) => acc + num, 0);
console.log(sum); // 15
// How it works:
// acc starts at 0 (initial value)
// 0 + 1 = 1
// 1 + 2 = 3
// 3 + 3 = 6
// 6 + 4 = 10
// 10 + 5 = 15
// Find maximum
const max = numbers.reduce((acc, num) => {
return num > acc ? num : acc;
}, numbers[0]);
console.log(max); // 5
// Count occurrences
const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];
const count = fruits.reduce((acc, fruit) => {
acc[fruit] = (acc[fruit] || 0) + 1;
return acc;
}, {});
console.log(count); // {apple: 3, banana: 2, orange: 1}
// Flatten array
const nested = [[1, 2], [3, 4], [5, 6]];
const flat = nested.reduce((acc, arr) => acc.concat(arr), []);
console.log(flat); // [1, 2, 3, 4, 5, 6]
// Without initial value
const product = [2, 3, 4].reduce((acc, num) => acc * num);
console.log(product); // 24 (2 * 3 * 4)39. What does the find method return?
Difficulty: MediumType: MCQTopic: JS Arrays
- An array of all matching elements
- The first element that matches the condition
- The index of the first match
- True if element exists
The find method returns the first element that satisfies the test function.
It stops searching as soon as it finds a match, making it efficient.
If no element matches, it returns undefined.
Find is useful when you need to retrieve a single item from an array based on a condition.
If you need the index instead of the element, use findIndex.
Correct Answer: The first element that matches the condition
Example Code
const numbers = [5, 12, 8, 130, 44];
// Find first element greater than 10
const found = numbers.find(num => num > 10);
console.log(found); // 12 (stops at first match)
// Find in array of objects
const users = [
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' },
{ id: 3, name: 'Bob' }
];
const user = users.find(u => u.id === 2);
console.log(user); // { id: 2, name: 'Jane' }
// Returns undefined if not found
const notFound = numbers.find(num => num > 200);
console.log(notFound); // undefined
// Compare with filter (returns array)
const filtered = numbers.filter(num => num > 10);
console.log(filtered); // [12, 130, 44]
// findIndex returns index
const index = numbers.findIndex(num => num > 10);
console.log(index); // 1
// Practical use
const cart = [{id: 1, qty: 2}, {id: 2, qty: 1}];
const item = cart.find(item => item.id === 1);
if (item) {
item.qty++;
}40. What does this code do: const [a, b] = [1, 2, 3]?
Difficulty: MediumType: MCQTopic: Modern Syntax
- Creates array with a and b
- Assigns 1 to a and 2 to b, ignoring 3
- Throws an error
- Assigns all values to both a and b
Array destructuring extracts values from arrays into distinct variables.
It assigns values by position. Extra values in the array are ignored. Missing values become undefined.
You can skip elements using commas, swap variables, or use rest operator to collect remaining elements.
Destructuring makes code cleaner when working with arrays.
It was introduced in ES6 and is widely used in modern JavaScript.
Correct Answer: Assigns 1 to a and 2 to b, ignoring 3
Example Code
// Basic destructuring
const [a, b] = [1, 2, 3];
console.log(a); // 1
console.log(b); // 2
// 3 is ignored
// Missing values are undefined
const [x, y, z] = [1, 2];
console.log(z); // undefined
// Skip elements
const [first, , third] = [1, 2, 3];
console.log(first); // 1
console.log(third); // 3
// Rest operator
const [head, ...tail] = [1, 2, 3, 4, 5];
console.log(head); // 1
console.log(tail); // [2, 3, 4, 5]
// Swap variables
let num1 = 10;
let num2 = 20;
[num1, num2] = [num2, num1];
console.log(num1); // 20
console.log(num2); // 10
// Default values
const [p = 1, q = 2] = [5];
console.log(p); // 5
console.log(q); // 2 (default)
// Nested destructuring
const [m, [n, o]] = [1, [2, 3]];
console.log(m, n, o); // 1 2 3
41. Compare forEach, map, filter, and reduce. When should you use each?
Difficulty: MediumType: SubjectiveTopic: JS Arrays
These are the four most important array methods in JavaScript. Each serves a different purpose.
ForEach executes a function for each element but returns undefined. Use it for side effects like logging, updating DOM, or sending data. It does not create a new array. You cannot break out of forEach.
Map transforms each element and returns a new array of the same length. Use it when you need to convert or modify every element, like extracting properties, formatting data, or applying calculations. Original array stays unchanged.
Filter creates a new array containing only elements that pass a test. Use it to select items matching criteria, remove unwanted elements, or find all matches. Returns empty array if nothing matches.
Reduce accumulates array elements into a single value by applying a function. Use it for summing, finding max or min, flattening arrays, grouping data, or any operation combining all elements into one result. Takes accumulator and current value.
When to use what:
Use forEach for side effects without creating new array. Use map for transforming every element into new array. Use filter for selecting subset of elements. Use reduce for aggregating into single value.
All methods are chainable and promote functional programming by avoiding mutations.
Example Code
const numbers = [1, 2, 3, 4, 5];
// forEach - side effects, returns undefined
let sum1 = 0;
numbers.forEach(num => {
sum1 += num; // Updating external variable
});
console.log(sum1); // 15
// map - transform each, returns new array
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// filter - select elements, returns new array
const evens = numbers.filter(num => num % 2 === 0);
console.log(evens); // [2, 4]
// reduce - accumulate to single value
const sum2 = numbers.reduce((acc, num) => acc + num, 0);
console.log(sum2); // 15
// Chaining methods
const result = numbers
.filter(num => num > 2) // [3, 4, 5]
.map(num => num * 2) // [6, 8, 10]
.reduce((acc, num) => acc + num, 0); // 24
console.log(result); // 24
// Real world example
const users = [
{ name: 'John', age: 17, active: true },
{ name: 'Jane', age: 25, active: true },
{ name: 'Bob', age: 30, active: false }
];
// Get names of active adult users
const activeAdultNames = users
.filter(user => user.active && user.age >= 18)
.map(user => user.name);
console.log(activeAdultNames); // ['Jane']42. Explain the difference between for, while, and for of loops. When would you use each?
Difficulty: MediumType: SubjectiveTopic: JS Loops
JavaScript has multiple loop types suited for different scenarios.
The for loop is best when you know the number of iterations in advance or need an index counter. It has initialization, condition, and increment in one line. Use for loops for iterating with indices, counting, or when you need fine control over iteration.
The while loop is best when you do not know how many iterations are needed and want to continue until a condition becomes false. The condition is checked before each iteration. Use while loops for reading data until end, waiting for user input, or processing until a flag changes.
The for of loop introduced in ES6 iterates directly over values of iterables like arrays, strings, maps, or sets. It gives you the values, not indices. Use for of when you need values but not indices, for cleaner and more readable code. It works with break and continue.
For objects, use for in to iterate over properties, though Object keys or Object entries with for of is often cleaner.
Choose based on:
Whether you need indices. Whether iteration count is known. Whether you are working with arrays or objects.
Example Code
// for loop - when you need index or known iterations
const arr = ['a', 'b', 'c'];
for (let i = 0; i < arr.length; i++) {
console.log(i, arr[i]); // 0 'a', 1 'b', 2 'c'
}
// while loop - when iterations unknown
let data = getData();
while (data !== null) {
process(data);
data = getData(); // Keep going until null
}
// Practical while example
let attempts = 0;
let success = false;
while (!success && attempts < 3) {
success = tryOperation();
attempts++;
}
// for...of - when you need values, not indices
const fruits = ['apple', 'banana', 'cherry'];
for (const fruit of fruits) {
console.log(fruit); // Clean and simple
}
// for...of with strings
for (const char of 'hello') {
console.log(char); // h, e, l, l, o
}
// for...of with break
for (const num of [1, 2, 3, 4, 5]) {
if (num === 3) break;
console.log(num); // 1, 2
}
// for...in for objects (use carefully)
const person = { name: 'John', age: 30 };
for (const key in person) {
console.log(key, person[key]);
}
// Better way for objects
for (const [key, value] of Object.entries(person)) {
console.log(key, value);
}43. Explain push, pop, shift, and unshift methods. What is the difference between them?
Difficulty: EasyType: SubjectiveTopic: JS Arrays
These are the four basic methods for adding and removing elements from arrays.
Push adds one or more elements to the end of an array. It modifies the original array and returns the new length. Use push when building up an array or adding items to the end.
Pop removes the last element from an array. It modifies the original array and returns the removed element. Use pop when you need to remove from the end, like implementing a stack.
Shift removes the first element from an array. It modifies the original array, shifts all other elements down by one index, and returns the removed element. Use shift when you need to remove from the beginning, but note it is slower than pop.
Unshift adds one or more elements to the beginning of an array. It modifies the original array, shifts all existing elements up by one index, and returns the new length. Use unshift to add to the beginning, but note it is slower than push.
Performance note:
Push and pop are fast operations. Shift and unshift are slower because they require reindexing all elements.
For queue operations, consider using push and shift, or better yet, a proper queue data structure for large datasets.
Example Code
const arr = [1, 2, 3];
// push - add to end
const newLength = arr.push(4, 5);
console.log(arr); // [1, 2, 3, 4, 5]
console.log(newLength); // 5
// pop - remove from end
const last = arr.pop();
console.log(arr); // [1, 2, 3, 4]
console.log(last); // 5
// unshift - add to beginning
const length2 = arr.unshift(0, -1);
console.log(arr); // [-1, 0, 1, 2, 3, 4]
console.log(length2); // 6
// shift - remove from beginning
const first = arr.shift();
console.log(arr); // [0, 1, 2, 3, 4]
console.log(first); // -1
// Stack (LIFO - Last In First Out)
const stack = [];
stack.push(1); // Add to top
stack.push(2);
stack.push(3);
console.log(stack.pop()); // 3 (remove from top)
// Queue (FIFO - First In First Out)
const queue = [];
queue.push(1); // Add to end
queue.push(2);
queue.push(3);
console.log(queue.shift()); // 1 (remove from beginning)
// All modify original array
const original = [1, 2, 3];
original.push(4);
console.log(original); // [1, 2, 3, 4] (changed)
44. Explain the difference between find, findIndex, indexOf, and includes methods for searching arrays.
Difficulty: MediumType: SubjectiveTopic: JS Arrays
JavaScript provides several methods for searching arrays, each with different use cases.
The find method returns the first element that satisfies a test function. It stops at the first match and returns the element itself. Returns undefined if nothing matches. Use find when you need the actual element and have complex search criteria.
The findIndex method returns the index of the first element that satisfies a test function. Returns negative 1 if nothing matches. Use findIndex when you need the position, not the element.
The indexOf method searches for a specific value using strict equality. Returns the index of the first occurrence, or negative 1 if not found. Use indexOf for simple value searches. It cannot search with conditions.
The includes method checks if an array contains a specific value. Returns true or false. Use includes when you only need to know if something exists, not where it is.
Key differences:
Find and findIndex use callback functions for complex conditions. IndexOf and includes use direct value comparison. Find returns element, findIndex and indexOf return index, includes returns boolean.
For objects, use find or findIndex because indexOf compares references, not properties.
Example Code
const numbers = [5, 12, 8, 130, 44];
// find - returns element
const found = numbers.find(num => num > 10);
console.log(found); // 12
// findIndex - returns index
const index = numbers.findIndex(num => num > 10);
console.log(index); // 1
// indexOf - returns index (simple value)
const idx = numbers.indexOf(8);
console.log(idx); // 2
console.log(numbers.indexOf(100)); // -1 (not found)
// includes - returns boolean
const hasEight = numbers.includes(8);
console.log(hasEight); // true
console.log(numbers.includes(100)); // false
// Array of objects
const users = [
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' },
{ id: 3, name: 'Bob' }
];
// find - best for objects
const user = users.find(u => u.id === 2);
console.log(user); // { id: 2, name: 'Jane' }
// indexOf - doesn't work well for objects
const wrongIdx = users.indexOf({ id: 2, name: 'Jane' });
console.log(wrongIdx); // -1 (different reference!)
// findIndex - works with objects
const userIdx = users.findIndex(u => u.id === 2);
console.log(userIdx); // 1
// Check if user exists
const exists = users.some(u => u.name === 'Jane');
console.log(exists); // true45. Explain splice and slice methods. What is the difference and when would you use each?
Difficulty: MediumType: SubjectiveTopic: JS Arrays
Splice and slice are commonly confused because of similar names, but they do very different things.
The splice method modifies the original array by adding, removing, or replacing elements. It takes start index, number of elements to delete, and optional elements to add. Returns an array of deleted elements. Use splice when you need to change the array in place.
The slice method creates a shallow copy of a portion of an array. It takes start and end indices. It does not modify the original array. Returns a new array. Use slice when you need a copy without changing the original.
Key differences:
Splice mutates the array, slice does not. Splice can add and remove, slice only copies. Splice returns deleted elements, slice returns new array.
Memory trick:
Splice has p for permanent change. Slice has no p, so no permanent change.
Common use cases:
Splice for removing items, inserting items, or replacing items. Slice for copying arrays, getting subarrays, or implementing pagination.
Example Code
// SPLICE - modifies original array
const arr1 = [1, 2, 3, 4, 5];
// Remove elements
const removed = arr1.splice(2, 2); // Start at index 2, remove 2 elements
console.log(arr1); // [1, 2, 5] (modified!)
console.log(removed); // [3, 4] (deleted elements)
// Insert elements
arr1.splice(2, 0, 'a', 'b'); // Start at 2, remove 0, add 'a' and 'b'
console.log(arr1); // [1, 2, 'a', 'b', 5]
// Replace elements
arr1.splice(0, 2, 'x', 'y'); // Remove first 2, add 'x' and 'y'
console.log(arr1); // ['x', 'y', 'a', 'b', 5]
// Remove from end
const last = arr1.splice(-1); // Remove last element
console.log(last); // [5]
// SLICE - does NOT modify original
const arr2 = [1, 2, 3, 4, 5];
// Get portion
const portion = arr2.slice(1, 4); // From index 1 to 4 (not including 4)
console.log(portion); // [2, 3, 4]
console.log(arr2); // [1, 2, 3, 4, 5] (unchanged!)
// Copy entire array
const copy = arr2.slice();
console.log(copy); // [1, 2, 3, 4, 5]
// Get last n elements
const lastTwo = arr2.slice(-2);
console.log(lastTwo); // [4, 5]
// Negative indices
const middle = arr2.slice(1, -1);
console.log(middle); // [2, 3, 4]
46. How do you correctly declare a function in JavaScript?
Difficulty: EasyType: MCQTopic: JS Functions
- function = myFunc() {}
- function myFunc() {}
- declare function myFunc() {}
- def myFunc() {}
In JavaScript, you declare a function using the keyword 'function', followed by a name and parentheses.
Inside the curly braces, you write the code that will run when the function is called.
Function declarations are hoisted, meaning you can call them even before they appear in the code.
Functions are used to group related logic into one reusable block.
This makes your program cleaner, easier to read, and more efficient to maintain.
Correct Answer: function myFunc() {}
Example Code
function greet() {
console.log('Hello World');
}
greet(); // Output: Hello World47. What happens if a function does not have a return statement?
Difficulty: EasyType: MCQTopic: JS Functions
- It returns undefined
- It throws an error
- It returns null
- It stops the program
Every JavaScript function returns something.
If you do not write a return statement, it automatically returns undefined.
This means the function completes its work but does not give any value back to the caller.
Use a return statement when you want to send data out of a function.
Remember, console.log only displays the result — it does not return it.
Correct Answer: It returns undefined
Example Code
function add(a, b) {
const sum = a + b;
}
console.log(add(2, 3)); // undefined48. Which of the following defines a function expression?
Difficulty: MediumType: MCQTopic: JS Functions
- const sum = function(a, b) { return a + b; }
- function sum(a, b) { return a + b; }
- def sum(a, b): return a + b;
- sum(a, b) => a + b;
A function expression defines a function and assigns it to a variable.
It can be named or anonymous and behaves like any other value in JavaScript.
Function expressions are not hoisted, so you must define them before using them.
They are widely used in callbacks, event listeners, and higher-order functions.
Use const to prevent accidental reassignment and keep code predictable.
Correct Answer: const sum = function(a, b) { return a + b; }
Example Code
const sum = function(a, b) {
return a + b;
};
console.log(sum(5, 3)); // 849. Which statement about arrow functions is true?
Difficulty: MediumType: MCQTopic: Arrow Functions
- They have their own this binding
- They do not have their own this binding
- They must always have a return keyword
- They can be used only as methods
Arrow functions are short and modern alternatives to normal functions.
They do not create their own 'this' value. Instead, they use the 'this' from the outer scope.
This makes them perfect for callbacks where you want to use the same context.
They cannot be used as constructors and do not have an arguments object.
Use them for clean, compact logic, but avoid them in methods that rely on dynamic 'this'.
Correct Answer: They do not have their own this binding
Example Code
const person = {
name: 'John',
greet: () => console.log('Hello ' + this.name)
};
person.greet(); // 'Hello undefined' (no own this)50. What is the output of function greet(name = 'Guest') { return 'Hi ' + name; } greet();
Difficulty: EasyType: MCQTopic: JS Functions
- Hi undefined
- Hi null
- Hi Guest
- Error
Default parameters allow a function to use fallback values when no argument is given.
This avoids undefined values and makes code more reliable.
Defaults are set from left to right, so earlier parameters can be used in later defaults.
This feature was introduced in ES6 and is now common in modern JavaScript.
Correct Answer: Hi Guest
Example Code
function greet(name = 'Guest') {
return 'Hi ' + name;
}
console.log(greet()); // Hi Guest51. Which functions are hoisted in JavaScript?
Difficulty: MediumType: MCQTopic: Scope Hoisting
- Only arrow functions
- Only function declarations
- All functions
- Function expressions only
Hoisting means moving declarations to the top during compilation.
Only function declarations are hoisted, not function expressions or arrow functions.
That’s why you can call a declared function before writing it in code.
This behavior helps JavaScript interpret functions flexibly but can also cause confusion if misused.
Always declare clearly to keep your code readable.
Correct Answer: Only function declarations
Example Code
greet(); // Works!
function greet() {
console.log('Hello');
}
sayHi(); // Error
const sayHi = function() {
console.log('Hi');
};52. What does IIFE stand for?
Difficulty: MediumType: MCQTopic: JS Modules
- Immediately Invoked Function Expression
- Instant Internal Function Execution
- Inline Invoked Function Example
- Immediate Internal Function Event
IIFE means a function that runs immediately after it is defined.
It is wrapped in parentheses and then invoked right away.
IIFEs create a private scope, keeping variables safe from the global scope.
They are often used in modules and setup code.
Think of an IIFE as a self-running function used to protect data or initialize logic once.
Correct Answer: Immediately Invoked Function Expression
Example Code
(function() {
console.log('IIFE runs instantly');
})();53. What is the difference between parameters and arguments?
Difficulty: EasyType: MCQTopic: JS Functions
- They mean the same thing
- Parameters are values passed, arguments are variable names
- Parameters are variable names, arguments are actual values
- Parameters are optional, arguments are required
Parameters are placeholders defined in a function declaration.
Arguments are the real values passed when calling that function.
In short, parameters are like empty boxes, and arguments are what you put inside them.
If you pass fewer arguments than parameters, the missing ones become undefined.
Matching them correctly ensures your function works as expected.
Correct Answer: Parameters are variable names, arguments are actual values
Example Code
function add(a, b) { return a + b; }
console.log(add(2, 3)); // 2 and 3 are arguments; a and b are parameters54. Explain function scope in JavaScript. How does it differ from block scope?
Difficulty: MediumType: SubjectiveTopic: Scope Hoisting
Function scope means variables declared inside a function can be used only inside that function.
Before ES6, variables declared with var followed function scope. They were visible anywhere inside the function body.
With ES6, let and const introduced block scope, which limits the variable to the nearest pair of curly braces.
Block scope is tighter and safer because it avoids unwanted access outside its block.
Use let or const for predictable, bug-free behavior.
Example Code
function test() {
if (true) {
var a = 10;
let b = 20;
}
console.log(a); // 10 (function scoped)
// console.log(b); // Error (block scoped)
}
test();55. How does function hoisting work? Can you call a function before defining it?
Difficulty: MediumType: SubjectiveTopic: Scope Hoisting
Function declarations are hoisted to the top of their scope during compilation.
That means you can call a declared function before it appears in your code.
However, function expressions and arrow functions are not hoisted. They act like normal variables and exist only after assignment.
Calling them early causes a reference error.
Understanding hoisting helps you organize code properly and avoid unpredictable results.
Example Code
sayHi(); // Works
function sayHi() {
console.log('Hello');
}
// Not hoisted
// greet(); // Error
const greet = function() { console.log('Hi'); };56. Compare arrow functions and regular functions in JavaScript.
Difficulty: MediumType: SubjectiveTopic: Arrow Functions
Arrow functions and regular functions behave differently in how they handle 'this'.
Arrow functions do not create their own 'this'; they use the one from the outer scope.
Regular functions have their own 'this' and can be used as constructors.
Arrow functions are shorter and great for small callbacks, but avoid them in object methods.
In interviews, remember this rule: Arrow equals lexical this. Regular equals dynamic this.
Use arrow for compact logic, and regular when context matters.
Example Code
function normal() { console.log(this); }
const arrow = () => console.log(this);
const obj = {
normal,
arrow
};
obj.normal(); // obj context
obj.arrow(); // global or undefined (lexical this)57. What is the purpose of an IIFE in JavaScript? Give a practical use case.
Difficulty: MediumType: SubjectiveTopic: JS Modules
An IIFE, or Immediately Invoked Function Expression, runs as soon as it is defined.
It creates a private area where variables cannot be accessed from outside.
Developers used IIFEs before ES6 modules to protect code and avoid global variable pollution.
They are still useful for one-time setups, like initializing configurations or starting timers.
Think of IIFE as a self-executing container that keeps your code safe and clean.
Example Code
(function() {
const secret = 'hidden';
console.log('IIFE executed');
})();
// secret is not accessible outside
// console.log(secret); // Error58. What is a callback function? Why is it important in JavaScript?
Difficulty: MediumType: SubjectiveTopic: JS Functions
A callback is a function you pass into another function to be executed later.
It allows asynchronous actions, meaning your program can continue working while waiting for something to finish.
Callbacks are essential in event handling, API calls, and timers.
They prevent blocking and make JavaScript responsive.
Before Promises and async await, callbacks were the main way to handle asynchronous behavior.
Mastering callbacks builds a strong base for understanding Promises and async programming.
Example Code
function fetchData(callback) {
setTimeout(() => {
console.log('Data ready');
callback();
}, 1000);
}
fetchData(() => console.log('Callback executed'));59. What best describes a closure in JavaScript?
Difficulty: MediumType: MCQTopic: JS Closures
- An object that contains private properties
- A function bundled with references to its lexical scope
- A block of code executed only once
- A special ES6 class feature
A closure occurs when an inner function remembers and can access variables from its outer function’s scope, even after the outer function has finished executing.
Closures are used for data privacy, function factories, and maintaining state across calls.
Typical use cases include counters, once-only initializers, and event handlers.
Correct Answer: A function bundled with references to its lexical scope
Example Code
function makeCounter() {
let count = 0;
return () => ++count;
}
const inc = makeCounter();
console.log(inc()); // 1
console.log(inc()); // 260. What is the primary difference between == and === in JavaScript?
Difficulty: EasyType: MCQTopic: JS Operators
- == compares value and type; === compares only value
- == performs type coercion; === does not
- There is no difference
- === converts operands to the same type automatically
The loose equality operator (==) allows type coercion before comparison, which can lead to surprising results.
The strict equality operator (===) checks both value and type without coercion and is generally safer in most comparisons.
Correct Answer: == performs type coercion; === does not
Example Code
console.log(5 == '5'); // true (coercion)
console.log(5 === '5'); // false (different types)
61. Given the code below, what is the output order?
Difficulty: MediumType: MCQTopic: Event Loop
- Start → End → Timeout → Promise
- Start → Promise → End → Timeout
- Start → End → Promise → Timeout
- Promise → Start → End → Timeout
Synchronous logs run first, then microtasks (Promises) run before macrotasks (setTimeout).
Correct Answer: Start → End → Promise → Timeout
Example Code
console.log('Start');
setTimeout(() => console.log('Timeout'), 0);
Promise.resolve().then(() => console.log('Promise'));
console.log('End');62. Which statement is true about call, apply, and bind?
Difficulty: MediumType: MCQTopic: this Binding
- call and apply return a new function; bind executes immediately
- bind returns a new function; call and apply execute immediately
- apply changes this permanently; call does not
- call and bind both take arguments as arrays
call(thisArg, ...args) and apply(thisArg, argsArray) invoke the function immediately with a specified this. bind(thisArg, ...args) returns a new function with this and optional arguments pre-set.
Correct Answer: bind returns a new function; call and apply execute immediately
Example Code
function add(a, b){ return this.x + a + b; }
const ctx = { x: 10 };
console.log(add.call(ctx, 1, 2)); // 13
console.log(add.apply(ctx, [1, 2])); // 13
const bound = add.bind(ctx, 1);
console.log(bound(2)); // 1363. How do arrow functions handle the value of this?
Difficulty: MediumType: MCQTopic: this Binding
- They bind their own this at call time
- They have no this and always use globalThis
- They lexically capture this from the surrounding scope
- They require call/apply to set this
Arrow functions do not create their own this; instead, they use the this from the enclosing scope. This makes them great for callbacks, but not suitable as methods or constructors.
Correct Answer: They lexically capture this from the surrounding scope
Example Code
const obj = {
name: 'Ava',
regular(){ console.log(this.name); },
arrow: () => console.log(this && this.name)
};
obj.regular(); // 'Ava'
obj.arrow(); // undefined (lexical this from module/global scope)64. Which statement is correct regarding hoisting and the temporal dead zone (TDZ)?
Difficulty: MediumType: MCQTopic: Scope Hoisting
- var, let, and const are all initialized to undefined at the top
- let/const are hoisted but not initialized, making them inaccessible in TDZ
- Only var is hoisted; let/const are not hoisted at all
- TDZ applies only to function declarations
All declarations are hoisted. var is initialized to undefined, while let/const exist in the TDZ until their declaration is evaluated, preventing access before initialization.
Correct Answer: let/const are hoisted but not initialized, making them inaccessible in TDZ
Example Code
// console.log(a); // undefined (var)
// console.log(b); // ReferenceError (TDZ)
var a = 1;
let b = 2; const c = 3;
65. How does JavaScript handle property lookup on objects?
Difficulty: MediumType: MCQTopic: JS Prototypes
- It searches only own properties
- It searches own properties, then walks up the prototype chain
- It copies properties from the prototype to the object on access
- It throws an error if the property is not an own property
If a property isn’t found on the object itself, JavaScript continues searching in the object’s [[Prototype]] chain until it finds the property or reaches null.
Correct Answer: It searches own properties, then walks up the prototype chain
Example Code
const proto = { kind: 'proto' };
const obj = Object.create(proto);
obj.name = 'JS';
console.log(obj.kind); // 'proto' via prototype66. Which statement about Promise.all and Promise.race is correct?
Difficulty: MediumType: MCQTopic: Promises
- Promise.all settles when the first promise settles; Promise.race waits for all
- Both wait for all promises to settle
- Promise.all rejects immediately if any promise rejects; Promise.race settles on the first settled promise
- Promise.race only resolves and never rejects
Promise.all resolves to an array of results if all promises resolve, and rejects on the first rejection.
Promise.race settles as soon as any input promise settles (resolve or reject).
Correct Answer: Promise.all rejects immediately if any promise rejects; Promise.race settles on the first settled promise
Example Code
Promise.all([p1, p2])
.then(vals => console.log(vals))
.catch(err => console.error('ALL reject', err));
Promise.race([p1, p2])
.then(val => console.log('RACE', val))
.catch(err => console.error('RACE reject', err));67. Which is true about null and undefined in JavaScript?
Difficulty: EasyType: MCQTopic: JS Operators
- typeof null === 'null'
- null means intentional absence; undefined means not assigned
- They are strictly equal (===)
- Both are numbers
undefined is the default value for uninitialized variables or missing properties.
null is an explicit assignment indicating 'no value'. Note: typeof null returns 'object' due to a historical quirk.
Correct Answer: null means intentional absence; undefined means not assigned
Example Code
let a;
const b = null;
console.log(a === undefined); // true
console.log(b === null); // true
console.log(typeof null); // 'object'
68. Explain debounce and throttle. When would you use each?
Difficulty: MediumType: SubjectiveTopic: Debounce Throttle
Debounce delays execution until a burst of events stops. Use it for search input or resize handlers so a function runs only after the user stops typing/resizing.
Throttle guarantees execution at most once per interval during a continuous event stream. Use it for scroll or mousemove to run a handler at a controlled rate.
Both techniques improve performance and user experience.
Example Code
// Debounce
function debounce(fn, delay){
let t; return (...args) => { clearTimeout(t); t = setTimeout(() => fn(...args), delay); };
}
// Throttle
function throttle(fn, interval){
let last = 0; return (...args) => {
const now = Date.now();
if (now - last >= interval){ last = now; fn(...args); }
};
}69. Describe the JavaScript event loop and the difference between microtasks and macrotasks.
Difficulty: MediumType: SubjectiveTopic: Event Loop
JavaScript runs on a single thread. Synchronous code executes first; asynchronous callbacks are queued. The event loop pulls tasks (macrotasks) like setTimeout and I/O. After each task, the engine drains the microtask queue (Promises, queueMicrotask) before the next macrotask and before painting. Understanding this ordering explains why Promise callbacks often run before setTimeout(…, 0).
Example Code
console.log('A');
queueMicrotask(() => console.log('microtask'));
setTimeout(() => console.log('macrotask'), 0);
console.log('B');
// A, B, microtask, macrotask70. How does JavaScript resolve a property when it’s not found on the object itself?
Difficulty: MediumType: MCQTopic: JS Prototypes
- It throws an error immediately
- It copies the property from the prototype onto the object
- It walks up the prototype chain until null
- It returns undefined without checking prototypes
If a property is missing on the object, JavaScript consults the object’s [[Prototype]] and continues up the chain until it finds the property or reaches null.
This is called prototypal inheritance and explains why objects can appear to "have" methods they never defined directly.
Correct Answer: It walks up the prototype chain until null
Example Code
const proto = { kind: 'base' };
const obj = Object.create(proto);
obj.name = 'JS';
console.log(obj.kind); // 'base' (found via prototype chain)71. What happens to this when you detach a method from its object and call it as a plain function?
Difficulty: MediumType: MCQTopic: this Binding
- It still points to the original object
- It becomes undefined in strict mode (or global object otherwise)
- It becomes the function itself
- It throws a TypeError
In non–arrow functions, this is determined by the call site.
Calling a detached method as a plain function loses the original receiver, so this is undefined in strict mode (or globalThis in sloppy mode).
Correct Answer: It becomes undefined in strict mode (or global object otherwise)
Example Code
"use strict";
const user = {
name: 'Ava',
say() { console.log(this && this.name); }
};
const fn = user.say;
fn(); // undefined72. What does Object.create(proto) do that {} does not?
Difficulty: MediumType: MCQTopic: JS Prototypes
- It deep-copies properties from proto
- It sets proto as the new object's [[Prototype]]
- It seals the object
- It prevents adding new properties
Object.create(proto) creates a new object with its internal [[Prototype]] set to proto.
The object starts with no own properties unless you pass a second argument with property descriptors.
Correct Answer: It sets proto as the new object's [[Prototype]]
Example Code
const base = { greet(){ return 'hi'; } };
const o = Object.create(base);
console.log(Object.getPrototypeOf(o) === base); // true
console.log(o.greet()); // 'hi'73. How do hasOwnProperty and the in operator differ?
Difficulty: EasyType: MCQTopic: Object Props
- Both check only own properties
- hasOwnProperty checks own properties; in checks own + inherited
- in checks own properties; hasOwnProperty checks own + inherited
- They are identical
obj.hasOwnProperty('x') is true only if x is an own property.
'x' in obj returns true if x exists on the object or anywhere in its prototype chain.
Correct Answer: hasOwnProperty checks own properties; in checks own + inherited
Example Code
const proto = { a: 1 };
const o = Object.create(proto);
o.b = 2;
console.log(o.hasOwnProperty('a')); // false
console.log('a' in o); // true74. Which attributes can be controlled via Object.defineProperty?
Difficulty: MediumType: MCQTopic: Object Props
- writable, enumerable, configurable (and value/get/set)
- only writable
- only enumerable and configurable
- None; properties are fixed
Data descriptors can control value, writable, enumerable, configurable.
Accessor descriptors can control get, set, enumerable, configurable.
By default, properties defined via defineProperty are not enumerable and not configurable unless set.
Correct Answer: writable, enumerable, configurable (and value/get/set)
Example Code
const o = {};
Object.defineProperty(o, 'x', { value: 42, writable: false });
// o.x = 5; // TypeError in strict mode
console.log(Object.getOwnPropertyDescriptor(o, 'x'));75. Which statement about Object.assign and spread {...obj} is true?
Difficulty: MediumType: MCQTopic: Object Copy
- They both perform deep copies
- They both perform shallow copies
- Object.assign is deep; spread is shallow
- Spread is deep; Object.assign is shallow
Object.assign and object spread create a new object but copy references for nested objects.
To deeply clone, you need a custom routine, a library, or structuredClone (for supported types).
Correct Answer: They both perform shallow copies
Example Code
const a = { n: 1, inner: { x: 10 } };
const b = { ...a };
b.inner.x = 99;
console.log(a.inner.x); // 99 (shallow copy)76. Why might Map be preferred over plain Object for key–value storage?
Difficulty: MediumType: MCQTopic: Map Set
- Map keys must be strings only
- Map preserves insertion order and allows keys of any type
- Map is always faster in all cases
- Object cannot be iterated
Map is designed for key–value collections with arbitrary key types and predictable iteration order.
Objects are best for structured data and use string/symbol keys; they also inherit from Object.prototype unless created with null prototype.
Correct Answer: Map preserves insertion order and allows keys of any type
Example Code
const m = new Map();
const k = { id: 1 };
m.set(k, 'value');
console.log(m.get(k)); // 'value'
console.log([...m]);77. Which option correctly contrasts instanceof and typeof?
Difficulty: MediumType: MCQTopic: JS Operators
- typeof checks prototype chains; instanceof reads internal type tags
- typeof is for primitives; instanceof checks prototype chains for objects
- Both work identically for arrays
- instanceof works on primitives; typeof works on objects only
typeof reports primitive types (except that typeof null is 'object') and 'function' for functions.
instanceof tests whether an object’s prototype chain contains a constructor’s prototype.
Correct Answer: typeof is for primitives; instanceof checks prototype chains for objects
Example Code
console.log(typeof 42); // 'number'
console.log(typeof null); // 'object' (quirk)
console.log([] instanceof Array); // true
78. What is a practical use of getters and setters on objects?
Difficulty: MediumType: MCQTopic: Object Props
- To freeze the object
- To compute values lazily or validate assignments
- To make properties enumerable by default
- They are required for class fields
Getters can compute a property on access; setters can validate or transform inputs.
They are defined either in object literals or via Object.defineProperty with get/set.
Correct Answer: To compute values lazily or validate assignments
Example Code
const user = {
_age: 20,
get age(){ return this._age; },
set age(v){ if(v>=0) this._age = v; }
};
user.age = -5; // ignored by setter
console.log(user.age); // 2079. Explain prototypal inheritance in JavaScript with a simple example.
Difficulty: MediumType: SubjectiveTopic: JS Prototypes
In JavaScript, objects inherit directly from other objects.
Every object has an internal [[Prototype]] pointing to another object (or null). When a property is missing, the engine looks up the chain.
This model enables sharing behavior without copying methods to each instance.
Example Code
const animal = { speak(){ return '...'; } };
const dog = Object.create(animal);
dog.speak = function(){ return 'woof'; };
const beagle = Object.create(dog);
console.log(beagle.speak()); // 'woof' (found on dog, not animal)80. How do ES6 classes relate to prototypes under the hood?
Difficulty: MediumType: SubjectiveTopic: JS Classes
ES6 class syntax is syntactic sugar over the existing prototype system.
Methods declared in a class body are added to the constructor’s prototype; static methods go on the constructor itself.
Inheritance (extends) sets up the prototype chain between constructors and their prototypes.
Example Code
class Person { constructor(name){ this.name = name; } greet(){ return 'Hi ' + this.name; } }
class Dev extends Person { code(){ return 'coding'; } }
console.log(Object.getOwnPropertyNames(Person.prototype)); // ['constructor','greet']81. Compare Object.preventExtensions, Object.seal, and Object.freeze.
Difficulty: MediumType: SubjectiveTopic: Object Props
preventExtensions: disallows adding new properties.
seal: disallows adding/removing properties and marks existing properties as non-configurable (values can still change if writable).
freeze: like seal, but also makes data properties non-writable (no changes allowed to values).
Example Code
const obj = { x: 1 };
Object.preventExtensions(obj);
// obj.y = 2; // TypeError in strict mode
const sealed = Object.seal({ a: 1 });
// delete sealed.a; // false
sealed.a = 5; // allowed
const frozen = Object.freeze({ b: 1 });
// frozen.b = 2; // TypeError in strict mode82. How do you create an object in JavaScript?
Difficulty: EasyType: MCQTopic: JS Objects
- object = (key: value)
- object = {key: value}
- object = [key: value]
- object = <key: value>
Objects are created using curly braces with key value pairs separated by commas.
Keys are property names and values can be any data type. You can also use the new Object syntax but curly brace notation is preferred.
Objects store data as key value pairs, also called properties.
You can add, modify, or delete properties after creation.
Objects are one of the most important data structures in JavaScript.
Correct Answer: object = {key: value}
Example Code
// Object literal (most common)
const person = {
name: 'John',
age: 30,
city: 'New York'
};
// Empty object
const empty = {};
// Using new Object (less common)
const obj = new Object();
obj.name = 'Jane';
// Accessing properties
console.log(person.name); // 'John' (dot notation)
console.log(person['age']); // 30 (bracket notation)
// Adding properties
person.job = 'Developer';
// Modifying properties
person.age = 31;
// Deleting properties
delete person.city;
// Nested objects
const user = {
name: 'John',
address: {
street: '123 Main St',
city: 'Boston'
}
};83. What does Object.keys() return?
Difficulty: MediumType: MCQTopic: JS Objects
- An object with all keys
- An array of property names
- An array of property values
- A string of keys
Object keys returns an array containing all enumerable property names of an object.
It only returns own properties, not inherited ones.
The order matches the order in which properties were added.
This is useful for iterating over object properties or checking if an object has any properties.
Combine with map or forEach to process object properties.
Correct Answer: An array of property names
Example Code
const person = {
name: 'John',
age: 30,
city: 'New York'
};
// Get all keys
const keys = Object.keys(person);
console.log(keys); // ['name', 'age', 'city']
// Check if object is empty
const isEmpty = Object.keys(person).length === 0;
console.log(isEmpty); // false
// Iterate over keys
Object.keys(person).forEach(key => {
console.log(key, person[key]);
});
// Get values
const values = Object.values(person);
console.log(values); // ['John', 30, 'New York']
// Get key-value pairs
const entries = Object.entries(person);
console.log(entries);
// [['name', 'John'], ['age', 30], ['city', 'New York']]
// Convert back to object
const obj = Object.fromEntries(entries);
console.log(obj); // {name: 'John', age: 30, city: 'New York'}84. What is the result of {a: 1} === {a: 1}?
Difficulty: MediumType: MCQTopic: Object Copy
Objects are compared by reference, not by value.
Even if two objects have identical properties, they are different objects in memory.
Triple equals checks if both variables point to the same object in memory.
To compare object contents, you need to manually compare each property or use a deep equality function.
This is a common source of confusion for beginners.
Correct Answer: false
Example Code
// Objects compared by reference
console.log({a: 1} === {a: 1}); // false (different objects)
// Same reference
const obj1 = {a: 1};
const obj2 = obj1; // Same reference
console.log(obj1 === obj2); // true
// Primitives compared by value
console.log(5 === 5); // true
console.log('hi' === 'hi'); // true
// Array comparison (also by reference)
console.log([1, 2] === [1, 2]); // false
const arr1 = [1, 2];
const arr2 = arr1;
console.log(arr1 === arr2); // true
// Compare object contents manually
function areEqual(obj1, obj2) {
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
for (let key of keys1) {
if (obj1[key] !== obj2[key]) return false;
}
return true;
}
console.log(areEqual({a: 1}, {a: 1})); // true85. In a regular function, what does this refer to in the browser?
Difficulty: HardType: MCQTopic: this Binding
- The function itself
- undefined
- The window object
- null
In non-strict mode, this in a regular function refers to the global object, which is window in browsers.
In strict mode, this is undefined in regular functions.
The value of this depends on how the function is called, not where it is defined.
In object methods, this refers to the object. In constructors with new, this refers to the newly created object. In arrow functions, this is inherited from the parent scope.
Understanding this is crucial for object-oriented JavaScript.
Correct Answer: The window object
Example Code
// Global context (browser)
console.log(this); // window object
// Regular function
function show() {
console.log(this); // window (non-strict) or undefined (strict)
}
show();
// Object method
const obj = {
name: 'Object',
greet: function() {
console.log(this.name); // 'Object' (this = obj)
}
};
obj.greet();
// Arrow function (lexical this)
const obj2 = {
name: 'Object2',
greet: () => {
console.log(this.name); // undefined (this = window)
}
};
obj2.greet();
// Constructor
function Person(name) {
this.name = name; // this = new object
}
const person = new Person('John');
// Event handler
button.addEventListener('click', function() {
console.log(this); // button element
});
// Explicit binding
function greet() {
console.log(this.name);
}
const user = { name: 'Jane' };
greet.call(user); // 'Jane' (this = user)86. What does const {name, age} = person do?
Difficulty: MediumType: MCQTopic: Modern Syntax
- Creates object with name and age
- Extracts name and age properties into variables
- Deletes name and age from person
- Throws an error
Object destructuring extracts properties from objects into distinct variables.
It matches variable names to property names. If a property does not exist, the variable is undefined.
You can rename variables, set default values, or destructure nested objects.
Destructuring makes code cleaner when working with objects.
It was introduced in ES6 and is widely used in modern JavaScript, especially in React.
Correct Answer: Extracts name and age properties into variables
Example Code
const person = {
name: 'John',
age: 30,
city: 'New York'
};
// Basic destructuring
const {name, age} = person;
console.log(name); // 'John'
console.log(age); // 30
// Rename variables
const {name: userName, age: userAge} = person;
console.log(userName); // 'John'
console.log(userAge); // 30
// Default values
const {name, country = 'USA'} = person;
console.log(country); // 'USA' (default)
// Missing property
const {job} = person;
console.log(job); // undefined
// Rest operator
const {name: n, ...rest} = person;
console.log(n); // 'John'
console.log(rest); // {age: 30, city: 'New York'}
// Nested destructuring
const user = {
name: 'Jane',
address: {
city: 'Boston',
zip: '02101'
}
};
const {address: {city, zip}} = user;
console.log(city); // 'Boston'
console.log(zip); // '02101'
// Function parameters
function greet({name, age}) {
console.log(`${name} is ${age}`);
}
greet(person); // 'John is 30'87. What does Object.assign() do?
Difficulty: MediumType: MCQTopic: JS Objects
- Assigns a value to a property
- Copies properties from one or more sources to target
- Creates a new empty object
- Deletes properties from object
Object assign copies all enumerable properties from one or more source objects to a target object.
It returns the modified target object. Properties in later sources overwrite properties in earlier sources.
Object assign creates a shallow copy, meaning nested objects are still referenced.
It is commonly used for cloning objects or merging multiple objects.
The spread operator is now preferred over Object assign for most use cases.
Correct Answer: Copies properties from one or more sources to target
Example Code
// Merging objects
const obj1 = {a: 1, b: 2};
const obj2 = {c: 3, d: 4};
const merged = Object.assign({}, obj1, obj2);
console.log(merged); // {a: 1, b: 2, c: 3, d: 4}
// Cloning object
const original = {x: 1, y: 2};
const clone = Object.assign({}, original);
clone.x = 10;
console.log(original.x); // 1 (unchanged)
// Override properties
const defaults = {color: 'red', size: 'medium'};
const options = {size: 'large'};
const config = Object.assign({}, defaults, options);
console.log(config); // {color: 'red', size: 'large'}
// Modifying target
const target = {a: 1};
Object.assign(target, {b: 2}, {c: 3});
console.log(target); // {a: 1, b: 2, c: 3}
// Modern spread operator (preferred)
const merged2 = {...obj1, ...obj2};
const clone2 = {...original};
// Shallow copy warning
const nested = {x: 1, y: {z: 2}};
const shallowCopy = Object.assign({}, nested);
shallowCopy.y.z = 3;
console.log(nested.y.z); // 3 (also changed!)88. What does JSON.stringify() do?
Difficulty: EasyType: MCQTopic: JS Objects
- Converts string to object
- Converts object to JSON string
- Validates JSON syntax
- Compresses JSON data
JSON stringify converts a JavaScript object into a JSON string.
It is used to send data to servers, store data in local storage, or create deep copies.
Undefined, functions, and symbols are omitted from the output. Dates are converted to strings.
You can pass optional parameters for formatting and filtering.
JSON parse does the opposite, converting JSON string back to object.
Correct Answer: Converts object to JSON string
Example Code
const obj = {
name: 'John',
age: 30,
active: true
};
// Convert to JSON string
const json = JSON.stringify(obj);
console.log(json); // '{"name":"John","age":30,"active":true}'
console.log(typeof json); // 'string'
// Pretty print with indentation
const pretty = JSON.stringify(obj, null, 2);
console.log(pretty);
// {
// "name": "John",
// "age": 30,
// "active": true
// }
// Convert back to object
const parsed = JSON.parse(json);
console.log(parsed); // {name: 'John', age: 30, active: true}
// What gets omitted
const complex = {
name: 'John',
age: 30,
greet: function() {}, // Functions omitted
data: undefined, // undefined omitted
id: Symbol('id') // Symbols omitted
};
console.log(JSON.stringify(complex)); // '{"name":"John","age":30}'
// Deep clone using JSON
const original = {a: 1, b: {c: 2}};
const clone = JSON.parse(JSON.stringify(original));
clone.b.c = 3;
console.log(original.b.c); // 2 (unchanged)89. What are the two ways to access object properties?
Difficulty: EasyType: MCQTopic: JS Objects
- Dot notation and bracket notation
- Get method and set method
- Arrow notation and function notation
- Direct access and indirect access
JavaScript provides two ways to access object properties.
Dot notation is cleaner and more common. Use it when property names are valid identifiers.
Bracket notation is more flexible. Use it when property names have spaces, start with numbers, are stored in variables, or are computed dynamically.
Both work the same way but bracket notation is more powerful.
For setting properties, both notations work identically.
Correct Answer: Dot notation and bracket notation
Example Code
const person = {
name: 'John',
age: 30,
'favorite color': 'blue'
};
// Dot notation (most common)
console.log(person.name); // 'John'
person.age = 31;
// Bracket notation (more flexible)
console.log(person['name']); // 'John'
person['age'] = 32;
// When bracket notation is needed
// Property with spaces
console.log(person['favorite color']); // 'blue'
// person.favorite color // Syntax error!
// Property in variable
const prop = 'name';
console.log(person[prop]); // 'John'
// Computed property names
const key = 'age';
console.log(person[key]); // 32
// Dynamic access
function getValue(obj, property) {
return obj[property];
}
console.log(getValue(person, 'name')); // 'John'
// Property starting with number
const data = {'1st': 'first'};
console.log(data['1st']); // 'first'
// console.log(data.1st); // Syntax error!90. What does hasOwnProperty() check?
Difficulty: MediumType: MCQTopic: Object Props
- If property exists anywhere in prototype chain
- If property is directly on the object, not inherited
- If property value is not null
- If property is writable
HasOwnProperty checks if a property exists directly on the object, not on its prototype chain.
It returns true only for own properties, not inherited ones.
This is useful when iterating over objects to avoid processing inherited properties.
Always use hasOwnProperty in for in loops to check own properties.
Modern alternative is Object hasOwn for better safety.
Correct Answer: If property is directly on the object, not inherited
Example Code
const person = {
name: 'John',
age: 30
};
// Check own property
console.log(person.hasOwnProperty('name')); // true
console.log(person.hasOwnProperty('age')); // true
console.log(person.hasOwnProperty('city')); // false
// Inherited properties
console.log(person.hasOwnProperty('toString')); // false (inherited)
console.log('toString' in person); // true (checks prototype chain)
// Safe iteration with for...in
for (const key in person) {
if (person.hasOwnProperty(key)) {
console.log(key, person[key]);
}
}
// Modern alternative
const obj = {x: 1};
console.log(Object.hasOwn(obj, 'x')); // true (ES2022)
// Prototype example
function Animal() {}
Animal.prototype.species = 'animal';
const dog = new Animal();
dog.name = 'Buddy';
console.log(dog.hasOwnProperty('name')); // true (own)
console.log(dog.hasOwnProperty('species')); // false (inherited)
console.log(dog.species); // 'animal' (accessible but not own)91. What does Object.freeze() do?
Difficulty: MediumType: MCQTopic: Object Props
- Converts object to string
- Makes object immutable, preventing any changes
- Saves object to disk
- Creates a copy of the object
Object freeze makes an object immutable. You cannot add, delete, or modify properties.
Frozen objects cannot be changed in any way. Attempting changes fails silently in non-strict mode or throws errors in strict mode.
Freeze is shallow, meaning nested objects are not automatically frozen.
Use freeze to create constants or protect important data.
Object seal is similar but allows modifying existing properties.
Correct Answer: Makes object immutable, preventing any changes
Example Code
const obj = {
name: 'John',
age: 30
};
// Freeze object
Object.freeze(obj);
// Cannot modify
obj.name = 'Jane'; // Ignored (or error in strict mode)
console.log(obj.name); // 'John' (unchanged)
// Cannot add
obj.city = 'Boston'; // Ignored
console.log(obj.city); // undefined
// Cannot delete
delete obj.age; // Ignored
console.log(obj.age); // 30 (still there)
// Check if frozen
console.log(Object.isFrozen(obj)); // true
// Shallow freeze
const nested = Object.freeze({
name: 'John',
address: {city: 'NYC'}
});
// Cannot change top level
nested.name = 'Jane'; // Ignored
// CAN change nested object (not frozen)
nested.address.city = 'Boston'; // Works!
console.log(nested.address.city); // 'Boston'
// Deep freeze
function deepFreeze(obj) {
Object.freeze(obj);
Object.keys(obj).forEach(key => {
if (typeof obj[key] === 'object' && obj[key] !== null) {
deepFreeze(obj[key]);
}
});
return obj;
}92. Explain the this keyword in JavaScript. How does its value change in different contexts?
Difficulty: MediumType: SubjectiveTopic: JS Objects
The this keyword refers to the object that is executing the current function. Its value depends on how and where the function is called, not where it is defined.
In global context, this refers to the window object in browsers or global in Node JS.
In object methods, this refers to the object that owns the method. When you call object method, this inside the method is the object.
In regular functions called standalone, this is window in non-strict mode or undefined in strict mode.
In arrow functions, this is lexically inherited from the parent scope. Arrow functions do not have their own this binding.
In constructor functions with new, this refers to the newly created object.
In event handlers, this typically refers to the element that triggered the event.
You can explicitly set this using call, apply, or bind methods.
Common mistakes:
Losing this when passing methods as callbacks. Arrow functions in object methods not having access to the object. Not understanding that this is determined by call time, not definition time.
Understanding this is crucial for object-oriented programming and working with callbacks in JavaScript.
Example Code
// Global context
console.log(this); // window (browser) or global (Node)
// Object method
const person = {
name: 'John',
greet: function() {
console.log(this.name); // 'John' (this = person)
}
};
person.greet();
// Lost context
const greetFunc = person.greet;
greetFunc(); // undefined (this = window or undefined)
// Arrow function (lexical this)
const obj = {
name: 'Object',
regularFunc: function() {
console.log(this.name); // 'Object'
},
arrowFunc: () => {
console.log(this.name); // undefined (this from parent scope)
}
};
// Constructor function
function Person(name) {
this.name = name; // this = new empty object
}
const p = new Person('Jane');
console.log(p.name); // 'Jane'
// Explicit binding
function sayName() {
console.log(this.name);
}
const user = {name: 'Bob'};
sayName.call(user); // 'Bob' (this = user)
sayName.apply(user); // 'Bob' (this = user)
const bound = sayName.bind(user);
bound(); // 'Bob' (this permanently bound to user)
// Event handler
button.addEventListener('click', function() {
console.log(this); // button element
});
// Arrow function in method
const timer = {
seconds: 0,
start: function() {
setInterval(() => {
this.seconds++; // this = timer (inherited)
console.log(this.seconds);
}, 1000);
}
};93. Explain Object.keys(), Object.values(), and Object.entries(). When would you use each?
Difficulty: MediumType: SubjectiveTopic: JS Objects
These three methods provide different ways to iterate over object properties.
Object keys returns an array of all enumerable property names (keys) from an object. Use it when you need to know what properties exist, check if object is empty, or iterate over property names. Returns empty array for empty objects.
Object values returns an array of all enumerable property values. Use it when you need the values but not the keys, like summing numbers or collecting all data. Does not include property names.
Object entries returns an array of key value pairs, where each pair is a two element array. Use it when you need both keys and values, like converting to Map, filtering properties, or iterating with both. Can be converted back to object with Object fromEntries.
All three methods:
Only return own enumerable properties, not inherited ones. Return arrays in the same order properties were added. Are commonly used with array methods like map, filter, and forEach.
When to use what:
Use keys when you need property names. Use values when you only need the data. Use entries when you need both and want to transform or filter properties.
These methods are essential for working with objects in a functional programming style.
Example Code
const person = {
name: 'John',
age: 30,
city: 'New York'
};
// Object.keys() - array of property names
const keys = Object.keys(person);
console.log(keys); // ['name', 'age', 'city']
// Check if empty
const isEmpty = Object.keys(person).length === 0;
// Iterate over keys
Object.keys(person).forEach(key => {
console.log(`${key}: ${person[key]}`);
});
// Object.values() - array of values
const values = Object.values(person);
console.log(values); // ['John', 30, 'New York']
// Sum numbers
const scores = {math: 90, english: 85, science: 95};
const total = Object.values(scores).reduce((sum, val) => sum + val, 0);
console.log(total); // 270
// Object.entries() - array of [key, value] pairs
const entries = Object.entries(person);
console.log(entries);
// [['name', 'John'], ['age', 30], ['city', 'New York']]
// Iterate with destructuring
for (const [key, value] of Object.entries(person)) {
console.log(`${key}: ${value}`);
}
// Filter properties
const filtered = Object.entries(person)
.filter(([key, value]) => typeof value === 'string')
.reduce((obj, [key, value]) => ({...obj, [key]: value}), {});
console.log(filtered); // {name: 'John', city: 'New York'}
// Convert to Map
const map = new Map(Object.entries(person));
// Convert back to object
const obj = Object.fromEntries(entries);
console.log(obj); // {name: 'John', age: 30, city: 'New York'}94. Explain the difference between shallow copy and deep copy. How do you create each in JavaScript?
Difficulty: HardType: SubjectiveTopic: Object Copy
Shallow copy creates a new object but copies only the top level properties. Nested objects are still referenced, not copied. Changes to nested objects affect both original and copy.
Deep copy creates a completely independent copy. All nested objects are also copied recursively. Changes to the copy do not affect the original at any level.
Creating shallow copies:
Use spread operator, Object assign, or Array slice. These are fast but only copy one level deep. Nested objects remain shared between original and copy.
Creating deep copies:
Use JSON parse and JSON stringify for simple objects without functions, dates, or special types. Use structured clone for more complete copying including dates and typed arrays. Use libraries like Lodash cloneDeep for complex objects. Write custom recursive function for specific needs.
When to use each:
Use shallow copy when objects are flat or you want to share nested references for performance. Use deep copy when you need complete independence and do not want any shared references.
Common pitfalls:
JSON methods lose functions, undefined, symbols, and dates. Shallow copies can cause unexpected mutations. Deep copying is slower and uses more memory.
Understanding the difference is crucial for avoiding bugs when working with complex data structures.
Example Code
// SHALLOW COPY
const original = {
name: 'John',
age: 30,
address: {city: 'NYC', zip: '10001'}
};
// Method 1: Spread operator
const shallow1 = {...original};
// Method 2: Object.assign
const shallow2 = Object.assign({}, original);
// Top level is copied
shallow1.name = 'Jane';
console.log(original.name); // 'John' (unchanged)
// Nested objects are referenced
shallow1.address.city = 'LA';
console.log(original.address.city); // 'LA' (changed!)
// Array shallow copy
const arr = [1, 2, [3, 4]];
const arrCopy = [...arr];
arrCopy[2][0] = 99;
console.log(arr[2][0]); // 99 (changed!)
// DEEP COPY
// Method 1: JSON (simple objects only)
const deep1 = JSON.parse(JSON.stringify(original));
deep1.address.city = 'Boston';
console.log(original.address.city); // 'NYC' (unchanged!)
// JSON limitations
const complex = {
name: 'John',
greet: function() {}, // Lost!
date: new Date(), // Becomes string
undef: undefined // Lost!
};
const jsonCopy = JSON.parse(JSON.stringify(complex));
console.log(jsonCopy.greet); // undefined (function lost)
// Method 2: structuredClone (modern)
const deep2 = structuredClone(original);
deep2.address.city = 'Chicago';
console.log(original.address.city); // 'NYC' (unchanged!)
// Method 3: Custom recursive function
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof Array) return obj.map(item => deepClone(item));
const cloned = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}
const deep3 = deepClone(original);
deep3.address.city = 'Miami';
console.log(original.address.city); // 'NYC' (unchanged!)95. Explain object destructuring in JavaScript. Include examples of renaming, default values, and nested destructuring.
Difficulty: MediumType: SubjectiveTopic: Modern Syntax
Object destructuring is a syntax for extracting multiple properties from an object into distinct variables in one statement. It matches variable names to property names.
Basic destructuring extracts properties into variables with the same names. If a property does not exist, the variable is undefined.
Renaming allows you to extract a property but assign it to a variable with a different name using colon syntax.
Default values provide fallback values when properties are undefined or do not exist.
Nested destructuring extracts properties from nested objects in one statement. You can go as deep as needed.
Rest operator collects remaining properties into a new object.
Function parameters can use destructuring to extract properties from objects passed as arguments. This is very common in React.
Benefits:
Cleaner code with less repetition. More readable when extracting multiple properties. Useful for function parameters. Common in modern JavaScript and frameworks.
Destructuring works with const, let, and var. It is one of the most useful ES6 features.
Example Code
const user = {
name: 'John',
age: 30,
email: 'john@example.com',
address: {
city: 'NYC',
country: 'USA'
}
};
// Basic destructuring
const {name, age} = user;
console.log(name); // 'John'
console.log(age); // 30
// Renaming variables
const {name: userName, email: userEmail} = user;
console.log(userName); // 'John'
console.log(userEmail); // 'john@example.com'
// Default values
const {name, phone = 'N/A'} = user;
console.log(phone); // 'N/A' (property doesn't exist)
const {age, role = 'user'} = user;
console.log(role); // 'user' (default)
// Nested destructuring
const {address: {city, country}} = user;
console.log(city); // 'NYC'
console.log(country); // 'USA'
// Rename nested property
const {address: {city: userCity}} = user;
console.log(userCity); // 'NYC'
// Rest operator
const {name: n, ...otherInfo} = user;
console.log(n); // 'John'
console.log(otherInfo); // {age: 30, email: '...', address: {...}}
// Function parameters
function greet({name, age}) {
console.log(`${name} is ${age} years old`);
}
greet(user); // 'John is 30 years old'
// With defaults in function
function createUser({name, role = 'guest', active = true}) {
return {name, role, active};
}
console.log(createUser({name: 'Jane'}));
// {name: 'Jane', role: 'guest', active: true}
// Array of objects
const users = [
{id: 1, name: 'John'},
{id: 2, name: 'Jane'}
];
const [{name: firstName}, {name: secondName}] = users;
console.log(firstName, secondName); // 'John' 'Jane'96. What are the different ways to create objects in JavaScript? Explain each method.
Difficulty: EasyType: SubjectiveTopic: JS Objects
JavaScript provides several ways to create objects, each with different use cases.
Object literal is the most common and simplest method. Use curly braces with key value pairs. Best for creating single objects or configuration objects. Clean and readable syntax.
Object constructor using new Object is less common. Functionally the same as object literal but more verbose. Rarely used in modern code.
Constructor functions use regular functions with new keyword. The function name is capitalized by convention. Inside the function, this refers to the new object being created. Good for creating multiple similar objects. This is the pre ES6 way of creating object types.
ES6 classes provide cleaner syntax for constructor functions. Classes use the class keyword with constructor method. Classes are syntactic sugar over constructor functions. Preferred in modern JavaScript for creating object types.
Object create method creates a new object with specified prototype. Useful for prototypal inheritance without constructor functions. Less common but powerful for advanced patterns.
Factory functions are regular functions that return objects. No new keyword needed. Provide more flexibility than classes. Good for encapsulation and private data.
When to use what:
Use object literals for one-off objects. Use classes for creating multiple instances with shared methods. Use factory functions for flexibility and private data. Use Object create for advanced prototypal inheritance.
Example Code
// 1. Object Literal (most common)
const person1 = {
name: 'John',
age: 30,
greet: function() {
console.log('Hello');
}
};
// 2. Object Constructor (rarely used)
const person2 = new Object();
person2.name = 'Jane';
person2.age = 25;
// 3. Constructor Function (pre-ES6)
function Person(name, age) {
this.name = name;
this.age = age;
this.greet = function() {
console.log(`Hi, I'm ${this.name}`);
};
}
const person3 = new Person('Bob', 35);
const person4 = new Person('Alice', 28);
// 4. ES6 Class (modern)
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hi, I'm ${this.name}`);
}
}
const user1 = new User('Charlie', 30);
const user2 = new User('Diana', 25);
// 5. Object.create()
const personProto = {
greet: function() {
console.log(`Hi, I'm ${this.name}`);
}
};
const person5 = Object.create(personProto);
person5.name = 'Eve';
person5.age = 22;
// 6. Factory Function
function createPerson(name, age) {
return {
name,
age,
greet() {
console.log(`Hi, I'm ${this.name}`);
}
};
}
const person6 = createPerson('Frank', 40);
const person7 = createPerson('Grace', 33);
// Comparison
console.log(person3 instanceof Person); // true
console.log(user1 instanceof User); // true
console.log(person6 instanceof Object); // true (but not specific type)97. What will this code output?
function outer() {
let count = 0;
return function inner() {
count++;
return count;
};
}
const counter = outer();
console.log(counter());
console.log(counter());
Difficulty: HardType: MCQTopic: JS Closures
- 1, 1
- 1, 2
- 0, 1
- undefined, undefined
The output is 1 then 2 because of closures.
A closure is when an inner function has access to variables from its outer function, even after the outer function has finished executing.
When outer is called, it creates a count variable and returns the inner function. The inner function remembers the count variable from its parent scope.
Each time counter is called, it increments the same count variable and returns it. The count persists between calls because the closure keeps it alive.
This is one of the most important concepts in JavaScript and appears in almost every interview.
Correct Answer: 1, 2
Example Code
function outer() {
let count = 0;
return function inner() {
count++;
return count;
};
}
const counter = outer();
console.log(counter()); // 1 (count is now 1)
console.log(counter()); // 2 (count is now 2)
console.log(counter()); // 3 (count is now 3)
// Each call to outer creates new closure
const counter2 = outer();
console.log(counter2()); // 1 (new count variable)
// Real world example - private variable
function createBankAccount(initialBalance) {
let balance = initialBalance; // Private variable
return {
deposit: function(amount) {
balance += amount;
return balance;
},
withdraw: function(amount) {
if (amount <= balance) {
balance -= amount;
return balance;
}
return 'Insufficient funds';
},
getBalance: function() {
return balance;
}
};
}
const account = createBankAccount(100);
console.log(account.deposit(50)); // 150
console.log(account.withdraw(30)); // 120
// console.log(account.balance); // undefined (private!)98. What is a callback function in JavaScript?
Difficulty: EasyType: MCQTopic: Async Basics
- A function that calls itself
- A function passed as an argument to another function
- A function that returns another function
- A function that runs automatically
A callback function is a function passed as an argument to another function, to be executed later.
Callbacks are used for asynchronous operations like reading files, making API calls, or handling events.
The function receiving the callback decides when to execute it.
Callbacks are fundamental to JavaScript's asynchronous programming model.
Modern JavaScript often uses Promises and async await instead of callbacks to avoid callback hell.
Correct Answer: A function passed as an argument to another function
Example Code
// Simple callback example
function greet(name, callback) {
console.log('Hello ' + name);
callback();
}
function sayBye() {
console.log('Goodbye!');
}
greet('John', sayBye);
// Output:
// Hello John
// Goodbye!
// Array methods use callbacks
const numbers = [1, 2, 3, 4, 5];
numbers.forEach(function(num) {
console.log(num * 2);
});
// Asynchronous callback
setTimeout(function() {
console.log('This runs after 2 seconds');
}, 2000);
// Event listener callback
button.addEventListener('click', function() {
console.log('Button clicked!');
});
// Callback with parameters
function processData(data, successCallback, errorCallback) {
if (data) {
successCallback(data);
} else {
errorCallback('No data');
}
}
processData('Hello',
function(result) { console.log('Success:', result); },
function(error) { console.log('Error:', error); }
);99. What are the three states of a Promise?
Difficulty: MediumType: MCQTopic: Promises
- start, middle, end
- pending, fulfilled, rejected
- waiting, success, failure
- begin, complete, error
A Promise has three states:
Pending is the initial state. The operation has started but not completed yet.
Fulfilled means the operation completed successfully. The promise has a resolved value.
Rejected means the operation failed. The promise has a rejection reason or error.
Once a promise is fulfilled or rejected, it stays in that state forever. It cannot change again.
Promises are used to handle asynchronous operations in a cleaner way than callbacks.
Correct Answer: pending, fulfilled, rejected
Example Code
// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
const success = true;
if (success) {
resolve('Operation successful!'); // Fulfilled
} else {
reject('Operation failed!'); // Rejected
}
});
// Using the Promise
myPromise
.then(result => {
console.log(result); // 'Operation successful!'
})
.catch(error => {
console.log(error);
});
// Real example - simulating API call
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const data = {name: 'John', age: 30};
resolve(data); // Fulfilled after 1 second
}, 1000);
});
}
fetchData()
.then(data => {
console.log('Data received:', data);
})
.catch(error => {
console.log('Error:', error);
});
// Chaining promises
fetchData()
.then(data => {
console.log('First then:', data);
return data.name;
})
.then(name => {
console.log('Second then:', name);
})
.catch(error => {
console.log('Error:', error);
})
.finally(() => {
console.log('Always runs');
});100. What does the async keyword do?
Difficulty: MediumType: MCQTopic: Async Await
- Makes function run faster
- Makes function always return a Promise
- Makes function run in background
- Makes function synchronous
The async keyword before a function makes it always return a Promise.
If the function returns a value, that value is automatically wrapped in a resolved Promise.
If the function throws an error, it returns a rejected Promise.
Inside an async function, you can use the await keyword to pause execution until a Promise resolves.
Async await provides cleaner syntax for working with Promises compared to then and catch chains.
Correct Answer: Makes function always return a Promise
Example Code
// Async function always returns Promise
async function greet() {
return 'Hello';
}
greet().then(result => {
console.log(result); // 'Hello'
});
// Same as:
function greetPromise() {
return Promise.resolve('Hello');
}
// Using await
async function fetchData() {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return data;
}
// Error handling with try catch
async function getData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.log('Error:', error);
}
}
// Multiple awaits
async function getMultipleData() {
const user = await fetchUser();
const posts = await fetchPosts(user.id);
const comments = await fetchComments(posts[0].id);
return {user, posts, comments};
}
// Parallel execution
async function getDataParallel() {
const [user, posts, comments] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchComments()
]);
return {user, posts, comments};
}101. What will this code output?
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
Difficulty: MediumType: MCQTopic: Async Basics
- 0, 1, 2
- 3, 3, 3
- undefined, undefined, undefined
- Error
The output is 3, 3, 3 because of how var and closures work with setTimeout.
Var has function scope, not block scope. All three setTimeout callbacks share the same i variable.
The loop completes immediately, setting i to 3. Then the callbacks execute and all see i as 3.
This is a classic interview question testing understanding of scope, closures, and the event loop.
To fix it, use let instead of var, which creates a new i for each iteration with block scope.
Correct Answer: 3, 3, 3
Example Code
// Problem with var
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// Output: 3, 3, 3
// Why? The loop runs:
// i = 0, schedule callback
// i = 1, schedule callback
// i = 2, schedule callback
// i = 3, loop ends
// Now callbacks run and all see i = 3
// Solution 1: Use let (block scope)
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// Output: 0, 1, 2
// Solution 2: IIFE (Immediately Invoked Function Expression)
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(() => console.log(j), 0);
})(i);
}
// Output: 0, 1, 2
// Solution 3: Pass parameter to setTimeout
for (var i = 0; i < 3; i++) {
setTimeout((num) => console.log(num), 0, i);
}
// Output: 0, 1, 2
// Understanding the event loop
console.log('Start');
setTimeout(() => console.log('Timeout'), 0);
Promise.resolve().then(() => console.log('Promise'));
console.log('End');
// Output: Start, End, Promise, Timeout
// (Microtasks run before macrotasks)102. What is a higher-order function?
Difficulty: MediumType: MCQTopic: Functional JS
- A function with many parameters
- A function that takes a function as argument or returns a function
- A function that runs multiple times
- A function declared at the top of the file
A higher-order function is a function that either takes one or more functions as arguments, or returns a function as its result.
Array methods like map, filter, and reduce are higher-order functions because they take callback functions as arguments.
Higher-order functions enable functional programming patterns and code reuse.
They are fundamental to JavaScript and used extensively in modern frameworks.
Understanding higher-order functions shows mastery of JavaScript functions and callbacks.
Examples: map, filter, reduce, forEach, setTimeout, and functions that return other functions.
Correct Answer: A function that takes a function as argument or returns a function
Example Code
// Takes function as argument
function repeat(n, action) {
for (let i = 0; i < n; i++) {
action(i);
}
}
repeat(3, console.log);
// Output: 0, 1, 2
// Returns a function
function multiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = multiplier(2);
const triple = multiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
// Array methods are higher-order functions
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);
const evens = numbers.filter(n => n % 2 === 0);
const sum = numbers.reduce((acc, n) => acc + n, 0);
// Practical example - once function
function once(fn) {
let called = false;
return function(...args) {
if (!called) {
called = true;
return fn(...args);
}
};
}
const initialize = once(() => {
console.log('Initialized!');
});
initialize(); // 'Initialized!'
initialize(); // (nothing happens)
initialize(); // (nothing happens)103. What is the output order?
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
Difficulty: HardType: MCQTopic: Event Loop
- 1, 2, 3, 4
- 1, 4, 2, 3
- 1, 4, 3, 2
- 1, 3, 4, 2
The output is 1, 4, 3, 2 because of how the event loop works.
JavaScript executes synchronous code first. So 1 and 4 print immediately.
Promises are microtasks and have higher priority than setTimeout which is a macrotask.
Microtasks run after the current script but before macrotasks. So 3 prints before 2.
Understanding the event loop, microtasks, and macrotasks is crucial for advanced JavaScript.
This is a very common and important interview question.
Correct Answer: 1, 4, 3, 2
Example Code
console.log('1'); // Synchronous - runs immediately
setTimeout(() => console.log('2'), 0); // Macrotask - queued
Promise.resolve().then(() => console.log('3')); // Microtask - queued
console.log('4'); // Synchronous - runs immediately
// Execution order:
// 1. Run all synchronous code: 1, 4
// 2. Run microtasks: 3
// 3. Run macrotasks: 2
// Output: 1, 4, 3, 2
// More complex example
console.log('Start');
setTimeout(() => {
console.log('Timeout 1');
Promise.resolve().then(() => console.log('Promise in timeout'));
}, 0);
Promise.resolve()
.then(() => {
console.log('Promise 1');
return Promise.resolve();
})
.then(() => console.log('Promise 2'));
setTimeout(() => console.log('Timeout 2'), 0);
console.log('End');
// Output:
// Start
// End
// Promise 1
// Promise 2
// Timeout 1
// Promise in timeout
// Timeout 2
// Event Loop Summary:
// 1. Synchronous code runs first
// 2. Microtasks (Promises, queueMicrotask)
// 3. Macrotasks (setTimeout, setInterval, I/O)104. What does Promise.all() do?
Difficulty: MediumType: MCQTopic: Promises
- Runs promises one after another
- Waits for all promises to resolve or any to reject
- Returns the first resolved promise
- Catches all promise errors
Promise all takes an array of promises and returns a single promise.
It waits for all promises to resolve successfully. If all succeed, it returns an array of all results.
If any promise rejects, Promise all immediately rejects with that error.
Use Promise all when you need all operations to complete and the order matters.
It runs promises in parallel, not sequentially, making it faster than awaiting promises one by one.
Correct Answer: Waits for all promises to resolve or any to reject
Example Code
// Promise.all() - all must succeed
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);
Promise.all([promise1, promise2, promise3])
.then(results => {
console.log(results); // [1, 2, 3]
});
// If any fails, all fails
const p1 = Promise.resolve(1);
const p2 = Promise.reject('Error!');
const p3 = Promise.resolve(3);
Promise.all([p1, p2, p3])
.then(results => console.log(results))
.catch(error => console.log(error)); // 'Error!'
// Practical example - fetching multiple APIs
async function getAllData() {
try {
const [users, posts, comments] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json())
]);
console.log({users, posts, comments});
} catch (error) {
console.log('One of the requests failed:', error);
}
}
// Promise.race() - first to finish wins
Promise.race([promise1, promise2, promise3])
.then(result => console.log(result)); // 1 (first to resolve)
// Promise.allSettled() - wait for all, get all results
Promise.allSettled([p1, p2, p3])
.then(results => console.log(results));
// [{status: 'fulfilled', value: 1},
// {status: 'rejected', reason: 'Error!'},
// {status: 'fulfilled', value: 3}]
// Promise.any() - first successful promise
Promise.any([p2, p1, p3])
.then(result => console.log(result)); // 1 (first fulfilled)105. How do you handle errors in async await?
Difficulty: MediumType: MCQTopic: Error Handling
- Use .catch() method
- Use try catch block
- Use error callback
- Errors cannot be caught
In async await, you handle errors using try catch blocks.
Wrap the await statements inside a try block. If any promise rejects, execution jumps to the catch block.
You can also use catch on the promise returned by the async function.
Finally block runs regardless of success or failure, useful for cleanup.
Proper error handling prevents your application from crashing and provides better user experience.
Correct Answer: Use try catch block
Example Code
// Using try catch
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.log('Error:', error.message);
} finally {
console.log('Cleanup code runs always');
}
}
// Multiple awaits with error handling
async function getUser(id) {
try {
const user = await fetchUser(id);
const posts = await fetchPosts(user.id);
const comments = await fetchComments(posts[0].id);
return {user, posts, comments};
} catch (error) {
console.log('Failed to get user data:', error);
return null;
}
}
// Using .catch() on async function
fetchData().catch(error => {
console.log('Caught:', error);
});
// Handling specific errors
async function getData() {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
if (error.name === 'TypeError') {
console.log('Network error');
} else {
console.log('Other error:', error);
}
throw error; // Re-throw if needed
}
}
// Without try catch (not recommended)
async function badExample() {
const data = await fetch('/api/data'); // Unhandled rejection!
return data;
}106. What is a closure? Explain with examples and common use cases.
Difficulty: HardType: SubjectiveTopic: JS Closures
Closure is created when an inner function remembers variables from its outer function scope even after the outer function has finished executing.
How closures work:
When you return a function from another function, the returned function carries its lexical environment with it. Variables from the outer function remain accessible to the inner function. The outer function's variables are kept alive in memory as long as the inner function exists.
Common use cases:
Data privacy and encapsulation. Creating private variables that cannot be accessed from outside. Factory functions that generate customized functions. Event handlers that need to remember state. Memoization for caching function results. Module pattern for organizing code.
Important points:
Closures can cause memory leaks if not managed properly. All inner functions share the same outer variables. Understanding closures is essential for advanced JavaScript.
Closures are one of the most important and frequently asked interview topics in JavaScript.
Example Code
// Basic closure example
function outer() {
let count = 0; // Private variable
return function inner() {
count++; // Accesses outer variable
return count;
};
}
const counter = outer();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
// count is kept alive by closure
// Data privacy
function createPerson(name) {
let age = 0; // Private
return {
getName: function() {
return name;
},
getAge: function() {
return age;
},
birthday: function() {
age++;
}
};
}
const person = createPerson('John');
console.log(person.getName()); // 'John'
console.log(person.getAge()); // 0
person.birthday();
console.log(person.getAge()); // 1
// console.log(person.age); // undefined (private!)
// Function factory
function multiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = multiplier(2);
const triple = multiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
// Event handler with closure
function setupButtons() {
for (let i = 0; i < 3; i++) {
const button = document.createElement('button');
button.textContent = 'Button ' + i;
button.addEventListener('click', function() {
console.log('Button ' + i + ' clicked');
// i is remembered by closure
});
}
}
// Memoization
function memoize(fn) {
const cache = {}; // Private cache
return function(...args) {
const key = JSON.stringify(args);
if (key in cache) {
console.log('From cache');
return cache[key];
}
const result = fn(...args);
cache[key] = result;
return result;
};
}
const expensiveOperation = memoize((n) => {
console.log('Computing...');
return n * n;
});
console.log(expensiveOperation(5)); // Computing... 25
console.log(expensiveOperation(5)); // From cache 25107. Explain Promises in JavaScript. How do they work and why are they better than callbacks?
Difficulty: MediumType: SubjectiveTopic: Promises
A Promise is an object representing the eventual completion or failure of an asynchronous operation.
Promises have three states:
Pending is the initial state. The operation has not completed yet.
Fulfilled means the operation completed successfully. The promise has a value.
Rejected means the operation failed. The promise has a reason for failure.
How Promises work:
You create a Promise with the new Promise constructor. Pass a function that receives resolve and reject callbacks. Call resolve with the result when operation succeeds. Call reject with the error when operation fails. Use .then to handle success and .catch to handle errors.
Why Promises are better than callbacks:
Cleaner syntax without deep nesting.
Can chain multiple operations easily. Better error handling with catch.
Can use Promise all for parallel operations.
Returns a value that can be passed around. Avoid callback hell.
Promise methods:
Then for handling success. Catch for handling errors. Finally for cleanup code. Promise all for waiting on multiple promises. Promise race for first completed promise. Promise allSettled for all results regardless of success or failure.
Promises are fundamental to modern JavaScript and understanding them is essential for working with async code.
Example Code
// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
// Asynchronous operation
setTimeout(() => {
const success = true;
if (success) {
resolve('Success!'); // Fulfilled
} else {
reject('Failed!'); // Rejected
}
}, 1000);
});
// Using the Promise
myPromise
.then(result => {
console.log(result); // 'Success!'
})
.catch(error => {
console.log(error);
})
.finally(() => {
console.log('Cleanup');
});
// Chaining Promises
fetchUser()
.then(user => {
console.log('Got user:', user);
return fetchPosts(user.id);
})
.then(posts => {
console.log('Got posts:', posts);
return fetchComments(posts[0].id);
})
.then(comments => {
console.log('Got comments:', comments);
})
.catch(error => {
console.log('Error in chain:', error);
});
// Promise.all() - parallel operations
Promise.all([
fetch('/api/users'),
fetch('/api/posts'),
fetch('/api/comments')
])
.then(([users, posts, comments]) => {
console.log('All data loaded');
})
.catch(error => {
console.log('One failed:', error);
});
// Converting callback to Promise
function readFilePromise(filename) {
return new Promise((resolve, reject) => {
fs.readFile(filename, 'utf8', (error, data) => {
if (error) {
reject(error);
} else {
resolve(data);
}
});
});
}
// Callback hell vs Promises
// Callback hell:
getData(function(a) {
getMoreData(a, function(b) {
getEvenMoreData(b, function(c) {
console.log(c);
});
});
});
// With Promises:
getData()
.then(a => getMoreData(a))
.then(b => getEvenMoreData(b))
.then(c => console.log(c));108. Explain async await in JavaScript. How does it make asynchronous code easier to write?
Difficulty: MediumType: SubjectiveTopic: Async Await
Async await is a modern syntax for handling asynchronous operations in JavaScript, built on top of Promises.
The async keyword:
Declare a function as async to make it always return a Promise. If the function returns a value, it is wrapped in a resolved Promise. If it throws an error, it returns a rejected Promise.
The await keyword:
Can only be used inside async functions. Pauses execution until the Promise resolves. Returns the resolved value. If Promise rejects, throws an error.
How it makes code easier:
Looks like synchronous code, easier to read. No need for then chains. Can use try catch for error handling. Can use regular loops and conditionals. Easier to debug than Promise chains.
Error handling:
Use try catch blocks around await statements. Can catch errors from multiple awaits in one catch block. Finally block for cleanup code.
Parallel execution:
Awaiting in sequence is slower. Use Promise all for parallel execution. Destructure results from Promise all.
Example Code
// Basic async await
async function fetchData() {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return data;
}
// Calling async function
fetchData()
.then(data => console.log(data))
.catch(error => console.log(error));
// Or with await (inside another async function)
async function main() {
const data = await fetchData();
console.log(data);
}
// Error handling with try catch
async function getUserData(id) {
try {
const user = await fetchUser(id);
const posts = await fetchPosts(user.id);
const comments = await fetchComments(posts[0].id);
return {user, posts, comments};
} catch (error) {
console.log('Error:', error.message);
return null;
} finally {
console.log('Cleanup');
}
}
// Sequential vs Parallel
// Sequential (slow - takes 6 seconds)
async function sequential() {
const user = await fetchUser(); // 2 seconds
const posts = await fetchPosts(); // 2 seconds
const comments = await fetchComments(); // 2 seconds
return {user, posts, comments};
}
// Parallel (fast - takes 2 seconds)
async function parallel() {
const [user, posts, comments] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchComments()
]);
return {user, posts, comments};
}
// Using async await with loops
async function processItems(items) {
for (const item of items) {
await processItem(item); // Sequential
}
}
// Parallel processing with Promise.all
async function processItemsParallel(items) {
await Promise.all(
items.map(item => processItem(item))
);
}
// Compare with Promises
// Promise way:
fetchUser()
.then(user => fetchPosts(user.id))
.then(posts => console.log(posts))
.catch(error => console.log(error));
// Async await way:
async function getAndLogPosts() {
try {
const user = await fetchUser();
const posts = await fetchPosts(user.id);
console.log(posts);
} catch (error) {
console.log(error);
}
}109. Explain the JavaScript event loop. How do microtasks and macrotasks differ?
Difficulty: HardType: SUBJECTIVETopic: Event Loop
The JavaScript event loop is the system that allows JavaScript to handle asynchronous operations even though it runs on a single thread.
JavaScript first runs all synchronous code on the call stack. When asynchronous work finishes—like timers, network requests, or Promises—their callbacks don’t run immediately. Instead, they’re placed into different queues. The event loop constantly checks: “Is the call stack empty?” When it is, it pulls tasks from these queues and executes them.
JavaScript has two important types of async tasks:
1. Microtasks (higher priority):
These include Promise .then() callbacks, queueMicrotask(), and MutationObserver.
Microtasks run right after the current synchronous code finishes, and the event loop will empty the entire microtask queue before moving on.
2. Macrotasks (lower priority):
These include setTimeout, setInterval, DOM events, and I/O callbacks.
The event loop processes one macrotask per cycle, and after each macrotask, it immediately runs all pending microtasks again.
Why the difference matters:
Because microtasks always run before the next macrotask, a Promise callback will run before a setTimeout(..., 0) every time.
Example Code
// Event loop example
console.log('1'); // Synchronous
setTimeout(() => console.log('2'), 0); // Macrotask
Promise.resolve().then(() => console.log('3')); // Microtask
console.log('4'); // Synchronous
// Output: 1, 4, 3, 2
// Explanation:
// 1. Sync code: 1, 4
// 2. Microtasks: 3
// 3. Macrotasks: 2
// Complex example
console.log('Start');
setTimeout(() => {
console.log('Timeout 1');
Promise.resolve().then(() => console.log('Promise in timeout'));
}, 0);
Promise.resolve()
.then(() => {
console.log('Promise 1');
setTimeout(() => console.log('Timeout in promise'), 0);
})
.then(() => console.log('Promise 2'));
setTimeout(() => console.log('Timeout 2'), 0);
console.log('End');
// Output:
// Start
// End
// Promise 1
// Promise 2
// Timeout 1
// Promise in timeout
// Timeout in promise
// Timeout 2
// Why Promises before setTimeout?
async function test() {
console.log('A');
setTimeout(() => console.log('B'), 0);
await Promise.resolve();
console.log('C');
setTimeout(() => console.log('D'), 0);
}
test();
console.log('E');
// Output: A, E, C, B, D
// Microtask queue vs Macrotask queue
queueMicrotask(() => console.log('Microtask'));
setTimeout(() => console.log('Macrotask'), 0);
// Microtask runs first
// Event loop phases:
// 1. Execute all synchronous code
// 2. Process all microtasks
// 3. Render (in browser)
// 4. Process one macrotask
// 5. Repeat from step 2110. What are higher-order functions? Provide examples and explain why they are useful.
Difficulty: MediumType: SubjectiveTopic: Functional JS
A higher-order function is a function that either takes one or more functions as arguments, or returns a function, or both.
Taking functions as arguments:
Array methods like map, filter, reduce, forEach are higher-order functions. Event listeners take callback functions. setTimeout and setInterval take functions. Custom functions can accept functions to make them reusable.
Returning functions:
Create specialized functions from generic ones. Implement currying and partial application. Build function factories. Create closures for data privacy.
Why they are useful:
Code reuse by passing different behaviors. Abstraction of common patterns. Functional programming style. More flexible and composable code. Easier to test and maintain.
Common higher-order function patterns:
Map for transforming arrays. Filter for selecting elements. Reduce for aggregating data. Compose for combining functions. Memoize for caching. Throttle and debounce for rate limiting.
Benefits:
DRY principle, do not repeat yourself. Declarative rather than imperative code. Easier to reason about. Better separation of concerns.
Higher-order functions are fundamental to functional programming and modern JavaScript. They enable powerful patterns and cleaner code. Understanding them is essential for advanced JavaScript development.
Example Code
// Array methods are higher-order functions
const numbers = [1, 2, 3, 4, 5];
// map - takes function, returns new array
const doubled = numbers.map(n => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// filter - takes function, returns filtered array
const evens = numbers.filter(n => n % 2 === 0);
console.log(evens); // [2, 4]
// reduce - takes function, returns single value
const sum = numbers.reduce((acc, n) => acc + n, 0);
console.log(sum); // 15
// Creating higher-order function
function repeat(times, action) {
for (let i = 0; i < times; i++) {
action(i);
}
}
repeat(3, console.log); // 0, 1, 2
// Returning a function
function multiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = multiplier(2);
const triple = multiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
// Practical example - once function
function once(fn) {
let called = false;
let result;
return function(...args) {
if (!called) {
called = true;
result = fn(...args);
}
return result;
};
}
const initialize = once(() => {
console.log('Initialized!');
return 'Ready';
});
console.log(initialize()); // 'Initialized!' 'Ready'
console.log(initialize()); // 'Ready' (no log)
// Compose functions
function compose(...fns) {
return function(value) {
return fns.reduceRight((acc, fn) => fn(acc), value);
};
}
const add5 = n => n + 5;
const multiply3 = n => n * 3;
const subtract2 = n => n - 2;
const calculate = compose(subtract2, multiply3, add5);
console.log(calculate(10)); // ((10 + 5) * 3) - 2 = 43
// Debounce (higher-order function)
function debounce(fn, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn(...args), delay);
};
}
const search = debounce((query) => {
console.log('Searching:', query);
}, 500);
// Memoization
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;
};
}111. What is the difference between slice() and substring()?
Difficulty: EasyType: MCQTopic: String Methods
- They are exactly the same
- slice() accepts negative indices, substring() does not
- substring() is faster than slice()
- slice() only works with arrays
Slice and substring both extract parts of a string, but they handle arguments differently.
Slice accepts negative indices. Negative numbers count from the end of the string. For example, slice minus 3 gets the last 3 characters.
Substring treats negative numbers as zero. It does not support counting from the end.
If start is greater than end, substring swaps them automatically. Slice returns an empty string instead.
Both create new strings without modifying the original.
Slice is more versatile and commonly preferred.
Correct Answer: slice() accepts negative indices, substring() does not
Example Code
const str = 'Hello World';
// slice() - accepts negative indices
console.log(str.slice(0, 5)); // 'Hello'
console.log(str.slice(6)); // 'World'
console.log(str.slice(-5)); // 'World' (last 5 chars)
console.log(str.slice(-5, -1)); // 'Worl' (from end)
// substring() - no negative support
console.log(str.substring(0, 5)); // 'Hello'
console.log(str.substring(6)); // 'World'
console.log(str.substring(-5)); // 'Hello World' (treats -5 as 0)
// Swapping behavior
console.log(str.slice(5, 0)); // '' (empty string)
console.log(str.substring(5, 0)); // 'Hello' (swaps to 0, 5)
// Get last character
console.log(str.slice(-1)); // 'd'
console.log(str.substring(str.length - 1)); // 'd'
// Both don't modify original
const original = 'Test';
const sliced = original.slice(0, 2);
console.log(original); // 'Test' (unchanged)
console.log(sliced); // 'Te'
112. Are strings mutable or immutable in JavaScript?
Difficulty: EasyType: MCQTopic: String Methods
- Mutable - you can change characters
- Immutable - you cannot change characters
- Depends on how they are declared
- Mutable with let, immutable with const
Strings are immutable in JavaScript. Once created, you cannot change individual characters.
Attempts to modify characters are ignored in non-strict mode or throw errors in strict mode.
String methods like replace, toUpperCase, and slice create new strings instead of modifying the original.
You can reassign the variable to a new string, but the original string value never changes.
This is different from arrays, which are mutable.
Immutability makes strings safer and more predictable.
Correct Answer: Immutable - you cannot change characters
Example Code
let str = 'Hello';
// Cannot change individual characters
str[0] = 'J';
console.log(str); // 'Hello' (unchanged)
// In strict mode, this would throw an error
'use strict';
// str[0] = 'J'; // TypeError in strict mode
// String methods create new strings
let original = 'hello';
let upper = original.toUpperCase();
console.log(original); // 'hello' (unchanged)
console.log(upper); // 'HELLO' (new string)
let text = 'JavaScript';
let replaced = text.replace('Java', 'Type');
console.log(text); // 'JavaScript' (unchanged)
console.log(replaced); // 'TypeScript' (new string)
// Can reassign variable (not modifying string)
let name = 'John';
name = 'Jane'; // New string assigned
// Compare with mutable arrays
let arr = [1, 2, 3];
arr[0] = 10; // Works! Arrays are mutable
console.log(arr); // [10, 2, 3]113. What are template literals in JavaScript?
Difficulty: EasyType: MCQTopic: Modern Syntax
- A way to create HTML templates
- Strings using backticks that support interpolation and multi-line
- A library for string manipulation
- A deprecated string feature
Template literals use backticks instead of quotes and provide powerful string features.
String interpolation lets you embed expressions using dollar sign and curly braces. The expression is evaluated and converted to a string.
Multi-line strings work naturally without needing escape characters.
You can embed any JavaScript expression, including function calls and calculations.
Template literals make string construction cleaner and more readable.
They were introduced in ES6 and are widely used in modern JavaScript.
Correct Answer: Strings using backticks that support interpolation and multi-line
Example Code
// Template literal syntax with backticks
const name = 'John';
const age = 30;
// String interpolation
const greeting = `Hello, my name is ${name} and I am ${age} years old`;
console.log(greeting);
// 'Hello, my name is John and I am 30 years old'
// Multi-line strings (no \n needed)
const multiLine = `
This is line 1
This is line 2
This is line 3
`;
console.log(multiLine);
// Expressions in template literals
const a = 5;
const b = 10;
console.log(`The sum is ${a + b}`); // 'The sum is 15'
console.log(`Double age: ${age * 2}`); // 'Double age: 60'
// Function calls
function getGreeting() {
return 'Hi';
}
console.log(`${getGreeting()}, ${name}!`); // 'Hi, John!'
// Nested template literals
const html = `
<div>
<h1>${name}</h1>
<p>Age: ${age}</p>
</div>
`;
// Compare with old way
const oldWay = 'Hello, my name is ' + name + ' and I am ' + age + ' years old';
// Conditional in template literal
const status = `User is ${age >= 18 ? 'adult' : 'minor'}`;
console.log(status); // 'User is adult'114. What does indexOf() return if the substring is not found?
Difficulty: MediumType: MCQTopic: String Methods
IndexOf returns minus 1 when the substring is not found in the string.
If found, it returns the index of the first occurrence, which is zero or a positive number.
You should check if the result equals minus 1 to determine if substring exists.
Never use indexOf in a boolean context without comparing, because zero is falsy but a valid index.
Modern alternative is the includes method which returns true or false.
LastIndexOf works similarly but searches from the end.
Correct Answer: -1
Example Code
const str = 'Hello World';
// indexOf() - returns index or -1
console.log(str.indexOf('World')); // 6 (found at index 6)
console.log(str.indexOf('o')); // 4 (first 'o')
console.log(str.indexOf('xyz')); // -1 (not found)
// Correct way to check existence
if (str.indexOf('World') !== -1) {
console.log('Found!');
}
// WRONG way (don't do this)
if (str.indexOf('Hello')) { // 0 is falsy!
console.log('This will not run!'); // Won't execute
}
// Modern alternative - includes()
console.log(str.includes('World')); // true
console.log(str.includes('xyz')); // false
if (str.includes('World')) {
console.log('Found!'); // Cleaner
}
// lastIndexOf() - searches from end
const text = 'hello hello';
console.log(text.indexOf('hello')); // 0 (first)
console.log(text.lastIndexOf('hello')); // 6 (last)
// Case sensitive
console.log(str.indexOf('world')); // -1 (lowercase not found)
// Starting position
console.log(str.indexOf('o', 5)); // 7 (search from index 5)115. Which method converts a string to uppercase?
Difficulty: EasyType: MCQTopic: String Methods
- uppercase()
- toUpper()
- toUpperCase()
- upper()
The toUpperCase method converts all characters in a string to uppercase letters.
It returns a new string and does not modify the original because strings are immutable.
The method has no parameters.
ToLowerCase works the opposite way, converting to lowercase.
Both methods are commonly used for case-insensitive comparisons.
They work with international characters, not just English letters.
Correct Answer: toUpperCase()
Example Code
const str = 'Hello World';
// toUpperCase() - all uppercase
const upper = str.toUpperCase();
console.log(upper); // 'HELLO WORLD'
console.log(str); // 'Hello World' (original unchanged)
// toLowerCase() - all lowercase
const lower = str.toLowerCase();
console.log(lower); // 'hello world'
// Case-insensitive comparison
const input = 'HELLO';
if (input.toLowerCase() === 'hello') {
console.log('Match!'); // This runs
}
// Works with international characters
const german = 'straße';
console.log(german.toUpperCase()); // 'STRASSE'
// Common use case - normalize input
function login(username, password) {
const normalizedUsername = username.toLowerCase();
// Compare with stored username
}
// Chain methods
const text = ' hello world ';
const result = text.trim().toUpperCase();
console.log(result); // 'HELLO WORLD'
// First letter uppercase
function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
}
console.log(capitalize('hELLO')); // 'Hello'116. What does the split() method return?
Difficulty: EasyType: MCQTopic: String Methods
- A string
- An array of substrings
- A number
- An object
The split method divides a string into an array of substrings based on a separator.
The separator can be a string or regular expression. It is removed from the result.
If separator is empty string, split returns array of individual characters.
You can limit the number of splits with a second parameter.
The join method does the opposite, combining array elements into a string.
Split and join are commonly used together for string manipulation.
Correct Answer: An array of substrings
Example Code
const str = 'Hello World JavaScript';
// Split by space
const words = str.split(' ');
console.log(words); // ['Hello', 'World', 'JavaScript']
// Split into characters
const chars = 'Hello'.split('');
console.log(chars); // ['H', 'e', 'l', 'l', 'o']
// Split with limit
const limited = str.split(' ', 2);
console.log(limited); // ['Hello', 'World']
// Split CSV
const csv = 'John,30,Developer';
const data = csv.split(',');
console.log(data); // ['John', '30', 'Developer']
// Join - opposite of split
const arr = ['Hello', 'World'];
const joined = arr.join(' ');
console.log(joined); // 'Hello World'
// Join with different separator
const path = ['folder', 'subfolder', 'file.txt'];
console.log(path.join('/')); // 'folder/subfolder/file.txt'
console.log(path.join('\\'));// 'folder\\subfolder\\file.txt'
// No separator in join (uses comma)
console.log(arr.join()); // 'Hello,World'
// Reverse string
const original = 'Hello';
const reversed = original.split('').reverse().join('');
console.log(reversed); // 'olleH'
// Count words
const sentence = 'This is a test';
const wordCount = sentence.split(' ').length;
console.log(wordCount); // 4117. What does the trim() method do?
Difficulty: EasyType: MCQTopic: String Methods
- Removes all spaces from string
- Removes whitespace from both ends of string
- Removes duplicate characters
- Shortens string to specified length
The trim method removes whitespace from both the beginning and end of a string.
Whitespace includes spaces, tabs, and newlines.
It does not remove whitespace from the middle of the string.
Trim returns a new string without modifying the original.
TrimStart removes only leading whitespace. TrimEnd removes only trailing whitespace.
Trim is commonly used to clean user input.
Correct Answer: Removes whitespace from both ends of string
Example Code
const str = ' Hello World ';
// trim() - removes whitespace from both ends
const trimmed = str.trim();
console.log(trimmed); // 'Hello World'
console.log(str); // ' Hello World ' (unchanged)
// trimStart() / trimLeft() - removes leading whitespace
const trimmedStart = str.trimStart();
console.log(trimmedStart); // 'Hello World '
// trimEnd() / trimRight() - removes trailing whitespace
const trimmedEnd = str.trimEnd();
console.log(trimmedEnd); // ' Hello World'
// Doesn't remove middle whitespace
const text = ' Hello World ';
console.log(text.trim()); // 'Hello World'
// Common use case - clean user input
function processInput(input) {
return input.trim().toLowerCase();
}
const userInput = ' HELLO ';
console.log(processInput(userInput)); // 'hello'
// Remove all whitespace (not just ends)
const removeAll = ' H e l l o ';
console.log(removeAll.replace(/\s/g, '')); // 'Hello'
// Trim works with tabs and newlines
const multiLine = '\n\t Hello \t\n';
console.log(multiLine.trim()); // 'Hello'
// Check if string is empty after trim
function isEmpty(str) {
return str.trim().length === 0;
}
console.log(isEmpty(' ')); // true
console.log(isEmpty(' hi ')); // false118. How many occurrences does replace() replace by default?
Difficulty: MediumType: MCQTopic: String Methods
- All occurrences
- Only the first occurrence
- Only the last occurrence
- Every other occurrence
The replace method only replaces the first occurrence of the search string.
To replace all occurrences, use replaceAll or a regular expression with the global flag.
Replace returns a new string without modifying the original.
The search value can be a string or regular expression.
The replacement value can be a string or function.
This is a common gotcha in JavaScript interviews.
Correct Answer: Only the first occurrence
Example Code
const str = 'hello hello hello';
// replace() - only first occurrence
const replaced = str.replace('hello', 'hi');
console.log(replaced); // 'hi hello hello'
// replaceAll() - all occurrences (ES2021)
const allReplaced = str.replaceAll('hello', 'hi');
console.log(allReplaced); // 'hi hi hi'
// Using regex with global flag
const regexReplace = str.replace(/hello/g, 'hi');
console.log(regexReplace); // 'hi hi hi'
// Case-insensitive replace
const text = 'Hello HELLO hello';
const caseInsensitive = text.replace(/hello/gi, 'hi');
console.log(caseInsensitive); // 'hi hi hi'
// Replace with function
const numbers = 'I have 2 apples and 3 oranges';
const doubled = numbers.replace(/\d+/g, (match) => {
return match * 2;
});
console.log(doubled); // 'I have 4 apples and 6 oranges'
// Remove all spaces
const spaced = 'H e l l o';
console.log(spaced.replace(/\s/g, '')); // 'Hello'
// Replace with captured groups
const date = '2024-01-15';
const formatted = date.replace(/(\d{4})-(\d{2})-(\d{2})/, '$3/$2/$1');
console.log(formatted); // '15/01/2024'
// Common mistake
const wrong = 'aaa'.replace('a', 'b');
console.log(wrong); // 'baa' (not 'bbb'!)119. What is the difference between charAt() and bracket notation for accessing characters?
Difficulty: EasyType: MCQTopic: String Methods
- They are exactly the same
- charAt() returns empty string for invalid index, bracket returns undefined
- bracket notation is faster
- charAt() can modify the character
CharAt and bracket notation both access characters at a specific index, but handle invalid indices differently.
CharAt returns an empty string for out-of-bounds indices.
Bracket notation returns undefined for out-of-bounds indices.
Both return the same result for valid indices.
Bracket notation is more modern and commonly used.
CharAt exists for backwards compatibility with older JavaScript.
Correct Answer: charAt() returns empty string for invalid index, bracket returns undefined
Example Code
const str = 'Hello';
// Both work for valid indices
console.log(str.charAt(0)); // 'H'
console.log(str[0]); // 'H'
console.log(str.charAt(4)); // 'o'
console.log(str[4]); // 'o'
// Different for invalid indices
console.log(str.charAt(10)); // '' (empty string)
console.log(str[10]); // undefined
console.log(str.charAt(-1)); // '' (empty string)
console.log(str[-1]); // undefined
// Common use cases
// Get first character
const first = str[0];
// Get last character
const last = str[str.length - 1];
// Check if character exists
if (str[5] !== undefined) {
console.log('Character exists');
}
// charCodeAt() - returns Unicode value
console.log(str.charCodeAt(0)); // 72 (Unicode for 'H')
// Loop through characters
for (let i = 0; i < str.length; i++) {
console.log(str[i]); // H, e, l, l, o
}
// Modern way - for...of
for (const char of str) {
console.log(char); // H, e, l, l, o
}120. Which is the most efficient way to concatenate strings in a loop?
Difficulty: EasyType: MCQTopic: String Methods
- Using + operator
- Using concat() method
- Using array join()
- Using template literals
For concatenating strings in a loop, array join is most efficient.
Using the plus operator in a loop creates a new string on each iteration, which is slow for large loops.
Push strings into an array, then join them at the end. This is much faster.
Modern JavaScript engines optimize string concatenation, but join is still best practice for loops.
For simple concatenation outside loops, plus operator or template literals are fine.
Concat method is rarely used in modern code.
Correct Answer: Using array join()
Example Code
// SLOW - using + in loop
let result1 = '';
for (let i = 0; i < 1000; i++) {
result1 += 'string' + i; // Creates new string each time
}
// FAST - using array join
const arr = [];
for (let i = 0; i < 1000; i++) {
arr.push('string' + i);
}
const result2 = arr.join(''); // One operation at end
// Simple concatenation (outside loops)
// Using + operator
const str1 = 'Hello' + ' ' + 'World';
// Using concat()
const str2 = 'Hello'.concat(' ', 'World');
// Using template literals (preferred)
const name = 'John';
const str3 = `Hello ${name}`;
// Array join for multiple strings
const parts = ['Hello', 'World', 'JavaScript'];
const joined = parts.join(' ');
console.log(joined); // 'Hello World JavaScript'
// Building HTML
const items = ['Apple', 'Banana', 'Orange'];
const html = items.map(item => `<li>${item}</li>`).join('');
console.log(html); // '<li>Apple</li><li>Banana</li><li>Orange</li>'
// Performance comparison
console.time('plus');
let s1 = '';
for (let i = 0; i < 10000; i++) s1 += i;
console.timeEnd('plus');
console.time('join');
const arr2 = [];
for (let i = 0; i < 10000; i++) arr2.push(i);
const s2 = arr2.join('');
console.timeEnd('join'); // Much faster!121. Explain the most commonly used string methods in JavaScript with examples.
Difficulty: MediumType: SubjectiveTopic: String Methods
JavaScript provides many built-in string methods for manipulation and searching.
Extraction methods:
Slice extracts a portion using start and end indices. Accepts negative indices to count from end. Most versatile extraction method.
Substring similar to slice but does not accept negative indices. Swaps arguments if start is greater than end.
Substr extracts from start index with specified length. Deprecated and should be avoided.
Search methods:
IndexOf finds first occurrence and returns index or minus 1. LastIndexOf searches from end.
Includes returns true or false if substring exists. Modern and cleaner than indexOf.
StartsWith checks if string begins with substring. EndsWith checks if string ends with substring.
Modification methods:
Replace replaces first occurrence. ReplaceAll replaces all occurrences.
ToUpperCase and toLowerCase change case.
Trim removes whitespace from both ends. TrimStart and trimEnd for one side only.
Split divides string into array based on separator.
Other useful methods:
Repeat repeats string specified number of times.
PadStart and padEnd add padding to reach target length.
CharAt accesses character at index.
All methods return new strings without modifying the original because strings are immutable.
Understanding these methods is essential for string manipulation in real applications.
Example Code
const str = 'JavaScript Programming';
// EXTRACTION
// slice(start, end)
console.log(str.slice(0, 10)); // 'JavaScript'
console.log(str.slice(-11)); // 'Programming'
console.log(str.slice(4, -1)); // 'Script Programmin'
// substring(start, end)
console.log(str.substring(0, 10)); // 'JavaScript'
console.log(str.substring(4, 10)); // 'Script'
// SEARCH
// indexOf(searchValue, startPosition)
console.log(str.indexOf('a')); // 1 (first 'a')
console.log(str.indexOf('a', 2)); // 3 (search from index 2)
console.log(str.indexOf('Python')); // -1 (not found)
// includes(searchValue)
console.log(str.includes('Script')); // true
console.log(str.includes('Python')); // false
// startsWith(), endsWith()
console.log(str.startsWith('Java')); // true
console.log(str.endsWith('ing')); // true
// MODIFICATION
// replace(search, replacement)
const text = 'I like cats. Cats are great.';
console.log(text.replace('cats', 'dogs'));
// 'I like dogs. Cats are great.' (only first)
console.log(text.replaceAll('cats', 'dogs'));
// 'I like dogs. dogs are great.' (case-sensitive!)
console.log(text.replace(/cats/gi, 'dogs'));
// 'I like dogs. dogs are great.' (case-insensitive)
// toUpperCase(), toLowerCase()
console.log(str.toUpperCase()); // 'JAVASCRIPT PROGRAMMING'
console.log(str.toLowerCase()); // 'javascript programming'
// trim(), trimStart(), trimEnd()
const spaced = ' Hello ';
console.log(spaced.trim()); // 'Hello'
console.log(spaced.trimStart()); // 'Hello '
console.log(spaced.trimEnd()); // ' Hello'
// split(separator, limit)
console.log(str.split(' ')); // ['JavaScript', 'Programming']
console.log('a,b,c'.split(','));// ['a', 'b', 'c']
console.log('hello'.split('')); // ['h','e','l','l','o']
// OTHER METHODS
// repeat(count)
console.log('Ha'.repeat(3)); // 'HaHaHa'
// padStart(length, padString)
console.log('5'.padStart(3, '0')); // '005'
console.log('5'.padEnd(3, '0')); // '500'
// charAt(index)
console.log(str.charAt(0)); // 'J'
console.log(str.charAt(100));// '' (empty string)
// concat(str1, str2, ...)
console.log('Hello'.concat(' ', 'World')); // 'Hello World'122. What are template literals and what are their advantages over regular strings? Explain with examples.
Difficulty: MediumType: SubjectiveTopic: Modern Syntax
Template literals are string literals using backticks that provide enhanced string functionality introduced in ES6.
Key features:
String interpolation allows embedding expressions using dollar sign and curly braces. Any JavaScript expression can be embedded including variables, calculations, and function calls.
Multi-line strings work naturally without escape characters. Just press enter to create new lines. This makes code much more readable.
Expression evaluation happens automatically. The result is converted to a string and inserted.
Advantages over regular strings:
Cleaner syntax for string concatenation. No need for plus operators.
Easier to read, especially with variables.
Natural multi-line support without backslash n.
Can embed any expression, not just variables.
Better for creating HTML templates.
Tagged templates for advanced use cases:
You can create custom string processing by tagging template literals with a function. The function receives string parts and values separately. Useful for internationalization, styling, or sanitization.
Common use cases:
Building dynamic messages.
Creating HTML markup.
SQL queries with parameters.
Formatting output.
Logging with context.
Template literals are one of the most useful ES6 features and are used extensively in modern JavaScript and frameworks like React.
Example Code
const name = 'John';
const age = 30;
const city = 'New York';
// STRING INTERPOLATION
// Old way
const oldWay = 'My name is ' + name + ', I am ' + age + ' years old';
// Template literal way (cleaner)
const newWay = `My name is ${name}, I am ${age} years old`;
console.log(newWay);
// EXPRESSIONS
// Any expression works
console.log(`Double age: ${age * 2}`);
console.log(`Next year: ${age + 1}`);
console.log(`Name length: ${name.length}`);
// Function calls
function getGreeting() {
return 'Hello';
}
console.log(`${getGreeting()}, ${name}!`);
// Ternary operators
const status = `User is ${age >= 18 ? 'adult' : 'minor'}`;
console.log(status);
// MULTI-LINE STRINGS
// Old way (messy)
const oldMultiLine = 'Line 1\n' +
'Line 2\n' +
'Line 3';
// Template literal way (natural)
const newMultiLine = `
Line 1
Line 2
Line 3
`;
// HTML TEMPLATES
const user = {
name: 'Jane',
email: 'jane@example.com',
role: 'Admin'
};
const userCard = `
<div class="user-card">
<h2>${user.name}</h2>
<p>Email: ${user.email}</p>
<span class="badge">${user.role}</span>
</div>
`;
// Dynamic lists
const items = ['Apple', 'Banana', 'Orange'];
const list = `
<ul>
${items.map(item => `<li>${item}</li>`).join('')}
</ul>
`;
// TAGGED TEMPLATES (advanced)
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
return result + str + (values[i] ? `<mark>${values[i]}</mark>` : '');
}, '');
}
const highlighted = highlight`Hello ${name}, you are ${age} years old`;
console.log(highlighted);
// 'Hello <mark>John</mark>, you are <mark>30</mark> years old'
// PRACTICAL EXAMPLES
// API URL building
const userId = 123;
const apiUrl = `https://api.example.com/users/${userId}/posts`;
// SQL queries (be careful with injection!)
const query = `
SELECT * FROM users
WHERE name = '${name}'
AND age > ${age}
`;
// Logging with context
console.log(`[${new Date().toISOString()}] User ${name} logged in`);
// Nested templates
const data = {
users: [{name: 'John', age: 30}, {name: 'Jane', age: 25}]
};
const table = `
<table>
${data.users.map(user => `
<tr>
<td>${user.name}</td>
<td>${user.age}</td>
</tr>
`).join('')}
</table>
`;123. How do you compare strings in JavaScript? Explain case-sensitive and case-insensitive comparison.
Difficulty: EasyType: SubjectiveTopic: String Methods
JavaScript provides multiple ways to compare strings.
Equality operators:
Triple equals compares strings by value and is case-sensitive. Returns true only if characters and case match exactly.
Double equals also compares by value but performs type coercion. For strings, it behaves the same as triple equals.
Always use triple equals for string comparison.
Case-sensitive comparison:
Direct comparison with triple equals. Capital and lowercase letters are different. This is the default behavior.
Case-insensitive comparison:
Convert both strings to same case using toLowerCase or toUpperCase before comparing. This is the standard approach.
LocaleCompare for sorting:
The localeCompare method compares strings for sorting purposes. Returns minus 1 if first string comes before second, 0 if equal, 1 if after. Supports locale-aware sorting for international characters. Can be case-insensitive with options.
Common patterns:
Check if strings match. Check if string starts or ends with substring. Check if string contains substring. Sort array of strings.
String comparison is case-sensitive by default, which catches many beginners. Always normalize case when needed for user input.
Example Code
// CASE-SENSITIVE COMPARISON (default)
const str1 = 'Hello';
const str2 = 'hello';
const str3 = 'Hello';
// Equality
console.log(str1 === str3); // true (exact match)
console.log(str1 === str2); // false (different case)
// Always use === for strings
console.log('hello' === 'hello'); // true
console.log('5' === '5'); // true
// CASE-INSENSITIVE COMPARISON
// Method 1: Convert to lowercase
const name1 = 'JOHN';
const name2 = 'john';
if (name1.toLowerCase() === name2.toLowerCase()) {
console.log('Names match!'); // This runs
}
// Method 2: Convert to uppercase
if (name1.toUpperCase() === name2.toUpperCase()) {
console.log('Names match!');
}
// PRACTICAL EXAMPLES
// User login (case-insensitive username)
function login(username, password) {
const storedUsername = 'JohnDoe';
const storedPassword = 'secret123';
if (username.toLowerCase() === storedUsername.toLowerCase() &&
password === storedPassword) {
return 'Login successful';
}
return 'Invalid credentials';
}
console.log(login('johndoe', 'secret123')); // 'Login successful'
console.log(login('JOHNDOE', 'secret123')); // 'Login successful'
// LOCALE COMPARE (for sorting)
const words = ['banana', 'Apple', 'cherry'];
// Default sort (lexicographic)
words.sort();
console.log(words); // ['Apple', 'banana', 'cherry']
// Case-insensitive sort
words.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
console.log(words); // ['Apple', 'banana', 'cherry']
// localeCompare returns -1, 0, or 1
console.log('a'.localeCompare('b')); // -1 (a comes before b)
console.log('b'.localeCompare('a')); // 1 (b comes after a)
console.log('a'.localeCompare('a')); // 0 (equal)
// Case-insensitive with localeCompare
const result = 'Hello'.localeCompare('hello', undefined, {sensitivity: 'base'});
console.log(result); // 0 (equal, ignoring case)
// SUBSTRING COMPARISON
const text = 'JavaScript Programming';
// startsWith (case-sensitive)
console.log(text.startsWith('Java')); // true
console.log(text.startsWith('java')); // false
// Case-insensitive startsWith
function startsWithIgnoreCase(str, search) {
return str.toLowerCase().startsWith(search.toLowerCase());
}
console.log(startsWithIgnoreCase(text, 'java')); // true
// includes (case-sensitive)
console.log(text.includes('Script')); // true
console.log(text.includes('script')); // false
// Case-insensitive includes
function includesIgnoreCase(str, search) {
return str.toLowerCase().includes(search.toLowerCase());
}
console.log(includesIgnoreCase(text, 'script')); // true124. How do you reverse a string in JavaScript? Explain multiple approaches.
Difficulty: MediumType: SubjectiveTopic: String Methods
There is no built-in method to reverse a string in JavaScript, but there are several approaches.
Approach 1: Split Reverse Join
Split string into array of characters. Use array reverse method. Join array back to string. This is the most common and readable approach.
Approach 2: For Loop
Loop through string backwards from end to start. Build new string by concatenating characters. More verbose but shows understanding of loops.
Approach 3: Reduce
Use reduce on array of characters. Prepend each character to accumulator. Functional programming style.
Approach 4: Recursion
Recursively take last character and add to reversed rest of string. Good for interviews to show recursion knowledge. Not practical for long strings due to stack limit.
Approach 5: Array From with Map
Use Array from with mapping function. Access characters in reverse order. More concise but less readable.
Best approach:
Split reverse join is the standard solution. It is readable, short, and efficient. Use this in production code.
Common mistakes:
Trying to reverse string directly. Forgetting strings are immutable. Not handling Unicode characters properly.
String reversal is a classic interview question testing array manipulation and understanding of immutability.
Example Code
const str = 'Hello World';
// APPROACH 1: Split, Reverse, Join (most common)
function reverseString1(str) {
return str.split('').reverse().join('');
}
console.log(reverseString1(str)); // 'dlroW olleH'
// One-liner
const reversed = str.split('').reverse().join('');
// APPROACH 2: For Loop (backwards)
function reverseString2(str) {
let reversed = '';
for (let i = str.length - 1; i >= 0; i--) {
reversed += str[i];
}
return reversed;
}
console.log(reverseString2(str)); // 'dlroW olleH'
// APPROACH 3: For Loop (forwards)
function reverseString3(str) {
let reversed = '';
for (let char of str) {
reversed = char + reversed; // Prepend
}
return reversed;
}
console.log(reverseString3(str));
// APPROACH 4: Reduce
function reverseString4(str) {
return str.split('').reduce((reversed, char) => char + reversed, '');
}
console.log(reverseString4(str));
// APPROACH 5: Recursion
function reverseString5(str) {
if (str === '') return '';
return reverseString5(str.slice(1)) + str[0];
}
console.log(reverseString5(str));
// Alternative recursion
function reverseString6(str) {
return str.length <= 1 ? str : reverseString6(str.slice(1)) + str[0];
}
// APPROACH 6: Array.from with map
function reverseString7(str) {
return Array.from(str, (_, i) => str[str.length - 1 - i]).join('');
}
console.log(reverseString7(str));
// APPROACH 7: Spread operator
function reverseString8(str) {
return [...str].reverse().join('');
}
console.log(reverseString8(str));
// PERFORMANCE COMPARISON
console.time('split-reverse-join');
for (let i = 0; i < 100000; i++) {
str.split('').reverse().join('');
}
console.timeEnd('split-reverse-join');
console.time('for-loop');
for (let i = 0; i < 100000; i++) {
let r = '';
for (let j = str.length - 1; j >= 0; j--) r += str[j];
}
console.timeEnd('for-loop');
// HANDLING UNICODE (emojis)
const emoji = 'Hello 👋 World 🌍';
// Wrong way (breaks emojis)
console.log(emoji.split('').reverse().join(''));
// '🌍 dlroW 👋 olleH' (might show broken emojis)
// Correct way for Unicode
function reverseStringUnicode(str) {
return [...str].reverse().join('');
}
console.log(reverseStringUnicode(emoji));
// PRACTICAL USE CASES
// Check if palindrome
function isPalindrome(str) {
const cleaned = str.toLowerCase().replace(/[^a-z0-9]/g, '');
return cleaned === cleaned.split('').reverse().join('');
}
console.log(isPalindrome('A man a plan a canal Panama')); // true
console.log(isPalindrome('hello')); // false125. How do you check if a string is empty or contains only whitespace in JavaScript?
Difficulty: EasyType: SubjectiveTopic: String Methods
Checking for empty or whitespace-only strings is common in form validation and data processing.
Empty string check:
Compare length property to zero. Or compare directly to empty string with triple equals. Or use logical NOT for falsy check, but be careful as it also catches undefined and null.
Whitespace-only check:
Use trim method and check if result is empty. This removes all leading and trailing whitespace. If trimmed string is empty, original contained only whitespace.
Common validation patterns:
Check if string exists and has content. Check if string is not just spaces. Validate user input before processing. Provide user feedback for invalid input.
Edge cases to consider:
Null and undefined are not empty strings. Zero is not an empty string. Tab and newline characters are whitespace. Empty array converted to string is empty string.
Best practices:
Always trim user input before validation. Check for both null or undefined and empty. Provide clear error messages. Use helper functions for reusability.
String validation is essential for building robust applications that handle user input correctly.
Example Code
// CHECK IF STRING IS EMPTY
// Method 1: Check length
function isEmpty(str) {
return str.length === 0;
}
console.log(isEmpty('')); // true
console.log(isEmpty('hello')); // false
// Method 2: Direct comparison
function isEmpty2(str) {
return str === '';
}
// Method 3: Falsy check (careful!)
if (!str) {
// This catches '', undefined, null
console.log('Empty or falsy');
}
// CHECK IF STRING IS EMPTY OR WHITESPACE
// Method 1: Trim and check (best)
function isEmptyOrWhitespace(str) {
return str.trim().length === 0;
}
console.log(isEmptyOrWhitespace('')); // true
console.log(isEmptyOrWhitespace(' ')); // true
console.log(isEmptyOrWhitespace('\t\n')); // true
console.log(isEmptyOrWhitespace('hello')); // false
// Method 2: Regex
function isEmptyOrWhitespace2(str) {
return /^\s*$/.test(str);
}
// HANDLE NULL/UNDEFINED
function isNullOrEmpty(str) {
return !str || str.trim().length === 0;
}
console.log(isNullOrEmpty(null)); // true
console.log(isNullOrEmpty(undefined)); // true
console.log(isNullOrEmpty('')); // true
console.log(isNullOrEmpty(' ')); // true
console.log(isNullOrEmpty('hello')); // false
// FORM VALIDATION EXAMPLE
function validateForm(formData) {
const errors = [];
// Check username
if (!formData.username || formData.username.trim().length === 0) {
errors.push('Username is required');
}
// Check email
if (!formData.email || formData.email.trim().length === 0) {
errors.push('Email is required');
}
// Check if string has minimum length
if (formData.password && formData.password.trim().length < 8) {
errors.push('Password must be at least 8 characters');
}
return errors;
}
const errors = validateForm({
username: ' ',
email: '',
password: 'short'
});
console.log(errors);
// ['Username is required', 'Email is required', 'Password must be...']
// REUSABLE VALIDATION HELPERS
const StringUtils = {
isEmpty: (str) => !str || str.length === 0,
isBlank: (str) => !str || str.trim().length === 0,
hasContent: (str) => str && str.trim().length > 0,
hasMinLength: (str, min) => str && str.trim().length >= min,
isNotEmpty: (str) => str && str.length > 0
};
// Usage
const input = ' hello ';
console.log(StringUtils.isEmpty(input)); // false
console.log(StringUtils.isBlank(input)); // false
console.log(StringUtils.hasContent(input)); // true
// EDGE CASES
console.log(isEmpty(null)); // TypeError (cannot read length)
console.log(isEmpty(undefined)); // TypeError
console.log(isEmpty(0)); // TypeError
// Safe check
function safeIsEmpty(str) {
return typeof str === 'string' && str.length === 0;
}
console.log(safeIsEmpty(null)); // false
console.log(safeIsEmpty('')); // true126. What is the prototype in JavaScript?
Difficulty: MediumType: MCQTopic: JS Prototypes
- A template for creating objects
- An object that other objects can inherit properties from
- A special type of function
- A method to copy objects
A prototype is an object from which other objects inherit properties and methods.
Every JavaScript object has a hidden property called prototype that points to another object.
When you access a property on an object, JavaScript first looks on the object itself. If not found, it looks in the prototype. This continues up the chain until the property is found or the chain ends.
Functions have a prototype property that is used when creating objects with the new keyword.
Prototypes enable inheritance in JavaScript without classes, though ES6 classes use prototypes under the hood.
Understanding prototypes is crucial for mastering JavaScript.
Correct Answer: An object that other objects can inherit properties from
Example Code
// Every object has a prototype
const obj = {};
console.log(Object.getPrototypeOf(obj)); // Object.prototype
// Function prototype property
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
console.log('Hello, ' + this.name);
};
const john = new Person('John');
john.greet(); // 'Hello, John'
// john doesn't have greet directly
console.log(john.hasOwnProperty('greet')); // false
console.log(john.hasOwnProperty('name')); // true
// greet comes from prototype
console.log(john.__proto__ === Person.prototype); // true
// Prototype chain
const arr = [1, 2, 3];
// arr -> Array.prototype -> Object.prototype -> null
console.log(arr.__proto__ === Array.prototype); // true
console.log(arr.__proto__.__proto__ === Object.prototype); // true
// Adding to prototype affects all instances
Person.prototype.sayBye = function() {
console.log('Bye, ' + this.name);
};
john.sayBye(); // 'Bye, John' (new method available)127. What is at the end of every prototype chain?
Difficulty: HardType: MCQTopic: JS Prototypes
- undefined
- null
- Object.prototype
- Function.prototype
The prototype chain ends with null.
Every object ultimately inherits from Object.prototype. The prototype of Object.prototype is null, which marks the end.
When JavaScript looks for a property, it goes up the chain until it finds the property or reaches null.
Reaching null means the property does not exist anywhere in the chain.
Understanding the prototype chain explains how inheritance and property lookup work in JavaScript.
This is a common interview question testing deep knowledge of prototypes.
Correct Answer: null
Example Code
// Prototype chain visualization
const obj = {};
// obj -> Object.prototype -> null
console.log(Object.getPrototypeOf(obj)); // Object.prototype
console.log(Object.getPrototypeOf(Object.prototype)); // null
// Array prototype chain
const arr = [];
// arr -> Array.prototype -> Object.prototype -> null
console.log(Object.getPrototypeOf(arr)); // Array.prototype
console.log(Object.getPrototypeOf(Array.prototype)); // Object.prototype
console.log(Object.getPrototypeOf(Object.prototype)); // null
// Function prototype chain
function myFunc() {}
// myFunc -> Function.prototype -> Object.prototype -> null
console.log(Object.getPrototypeOf(myFunc)); // Function.prototype
console.log(Object.getPrototypeOf(Function.prototype)); // Object.prototype
console.log(Object.getPrototypeOf(Object.prototype)); // null
// Property lookup example
const person = {
name: 'John'
};
// Looking for toString
// 1. Check person - not found
// 2. Check Object.prototype - found!
console.log(person.toString()); // '[object Object]'
// Looking for nonExistent
// 1. Check person - not found
// 2. Check Object.prototype - not found
// 3. Reach null - return undefined
console.log(person.nonExistent); // undefined128. What does the new keyword do in JavaScript?
Difficulty: MediumType: MCQTopic: JS Prototypes
- Creates a new function
- Creates a new object, sets prototype, binds this, returns object
- Copies an existing object
- Creates a new variable
The new keyword performs four steps when creating an object:
Step 1: Creates a new empty object.
Step 2: Sets the prototype of the new object to the constructor's prototype property.
Step 3: Calls the constructor function with this bound to the new object.
Step 4: Returns the new object, unless the constructor explicitly returns another object.
This is how object-oriented programming works in JavaScript before ES6 classes.
Forgetting new when calling a constructor can cause bugs because this will refer to the global object instead.
Correct Answer: Creates a new object, sets prototype, binds this, returns object
Example Code
// Constructor function
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
console.log('Hi, I am ' + this.name);
};
// Using new keyword
const john = new Person('John', 30);
// What new does:
// 1. Creates empty object: {}
// 2. Sets prototype: object.__proto__ = Person.prototype
// 3. Calls Person with this = object: Person.call(object, 'John', 30)
// 4. Returns object
console.log(john.name); // 'John'
console.log(john.age); // 30
john.greet(); // 'Hi, I am John'
// Check prototype
console.log(john.__proto__ === Person.prototype); // true
console.log(john instanceof Person); // true
// Forgetting new (common mistake)
const jane = Person('Jane', 25); // Forgot new!
console.log(jane); // undefined
console.log(window.name); // 'Jane' (polluted global!)
// Manual implementation of new
function myNew(constructor, ...args) {
// Step 1: Create empty object
const obj = {};
// Step 2: Set prototype
Object.setPrototypeOf(obj, constructor.prototype);
// Step 3: Call constructor
const result = constructor.apply(obj, args);
// Step 4: Return object (or result if object)
return typeof result === 'object' && result !== null ? result : obj;
}
const bob = myNew(Person, 'Bob', 35);
console.log(bob.name); // 'Bob'
bob.greet(); // 'Hi, I am Bob'129. What does the instanceof operator check?
Difficulty: MediumType: MCQTopic: JS Operators
- If object has a specific property
- If object's prototype chain includes constructor's prototype
- If object was created with new
- If object is of a primitive type
The instanceof operator checks if an object's prototype chain includes a constructor's prototype property.
It returns true if the constructor's prototype appears anywhere in the object's prototype chain.
Instanceof does not check if the object was directly created by that constructor, only if the prototype exists in the chain.
It returns false for primitive values.
Instanceof is useful for type checking and validation in JavaScript.
You can manipulate prototypes to trick instanceof, so it is not completely reliable.
Correct Answer: If object's prototype chain includes constructor's prototype
Example Code
function Person(name) {
this.name = name;
}
function Animal(type) {
this.type = type;
}
const john = new Person('John');
const dog = new Animal('Dog');
// Basic instanceof
console.log(john instanceof Person); // true
console.log(dog instanceof Animal); // true
console.log(john instanceof Animal); // false
// Everything is an instance of Object
console.log(john instanceof Object); // true
console.log(dog instanceof Object); // true
console.log([] instanceof Object); // true
// Arrays
const arr = [1, 2, 3];
console.log(arr instanceof Array); // true
console.log(arr instanceof Object); // true
// Primitives return false
console.log(5 instanceof Number); // false
console.log('hello' instanceof String); // false
console.log(true instanceof Boolean); // false
// Wrapper objects return true
console.log(new Number(5) instanceof Number); // true
// How instanceof works
function myInstanceof(obj, constructor) {
let proto = Object.getPrototypeOf(obj);
while (proto !== null) {
if (proto === constructor.prototype) {
return true;
}
proto = Object.getPrototypeOf(proto);
}
return false;
}
console.log(myInstanceof(john, Person)); // true
console.log(myInstanceof(john, Object)); // true
// Can be tricked by changing prototype
const obj = {};
console.log(obj instanceof Array); // false
Object.setPrototypeOf(obj, Array.prototype);
console.log(obj instanceof Array); // true (tricked!)130. What does Object.create() do?
Difficulty: MediumType: MCQTopic: JS Prototypes
- Creates a copy of an object
- Creates a new object with specified prototype
- Creates a new constructor function
- Creates a new class
Object.create creates a new object with the specified object as its prototype.
You pass the prototype object as the first argument. The new object will inherit from it.
Object.create allows creating objects without using constructor functions or classes.
It is the purest form of prototypal inheritance in JavaScript.
You can pass null to create an object with no prototype, useful for creating truly empty objects.
Object.create is commonly used in design patterns and frameworks.
Correct Answer: Creates a new object with specified prototype
Example Code
// Basic Object.create
const personProto = {
greet: function() {
console.log('Hello, ' + this.name);
}
};
const john = Object.create(personProto);
john.name = 'John';
john.greet(); // 'Hello, John'
// john inherits from personProto
console.log(Object.getPrototypeOf(john) === personProto); // true
// Create object with no prototype
const emptyObj = Object.create(null);
console.log(Object.getPrototypeOf(emptyObj)); // null
// No toString, hasOwnProperty, etc.
// Adding properties during creation
const jane = Object.create(personProto, {
name: {
value: 'Jane',
writable: true,
enumerable: true,
configurable: true
},
age: {
value: 25,
writable: true
}
});
jane.greet(); // 'Hello, Jane'
console.log(jane.age); // 25
// Inheritance pattern with Object.create
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(this.name + ' is eating');
};
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
// Set up inheritance
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log(this.name + ' says woof!');
};
const buddy = new Dog('Buddy', 'Golden Retriever');
buddy.eat(); // 'Buddy is eating'
buddy.bark(); // 'Buddy says woof!'
// Compare with direct assignment (wrong)
Dog.prototype = Animal.prototype; // Don't do this!
// This shares the same object, not creates inheritance131. Are ES6 classes truly different from constructor functions?
Difficulty: EasyType: MCQTopic: JS Classes
- Yes, classes are completely new feature
- No, classes are syntactic sugar over prototypes
- Yes, classes use different inheritance model
- No, they are exactly the same
ES6 classes are syntactic sugar over JavaScript's existing prototype-based inheritance.
Under the hood, classes still use prototypes and constructor functions.
Classes provide cleaner and more familiar syntax for developers from other languages.
Key differences: Class methods are non-enumerable by default. Classes must be called with new. Classes have stricter mode automatically.
The class syntax is preferred in modern JavaScript for creating objects, but understanding prototypes is still essential.
Classes make code more readable but do not change how JavaScript inheritance works internally.
Correct Answer: No, classes are syntactic sugar over prototypes
Example Code
// ES6 Class
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hi, I am ${this.name}`);
}
static create(name, age) {
return new Person(name, age);
}
}
// Equivalent constructor function
function PersonFunc(name, age) {
this.name = name;
this.age = age;
}
PersonFunc.prototype.greet = function() {
console.log('Hi, I am ' + this.name);
};
PersonFunc.create = function(name, age) {
return new PersonFunc(name, age);
};
// Both work the same way
const john = new Person('John', 30);
const jane = new PersonFunc('Jane', 25);
john.greet(); // 'Hi, I am John'
jane.greet(); // 'Hi, I am Jane'
// Both use prototypes
console.log(john.__proto__ === Person.prototype); // true
console.log(jane.__proto__ === PersonFunc.prototype); // true
// Class is still a function
console.log(typeof Person); // 'function'
// Key differences:
// 1. Class methods are non-enumerable
for (let key in john) {
console.log(key); // Only 'name' and 'age', not 'greet'
}
for (let key in jane) {
console.log(key); // 'name', 'age', and 'greet'
}
// 2. Classes must use new
// const bob = Person('Bob', 35); // TypeError!
// 3. Classes are in strict mode
class Test {
method() {
console.log(this); // undefined if called without object
}
}132. What does the super keyword do in a class?
Difficulty: MediumType: MCQTopic: JS Classes
- Creates a new class
- Calls the parent class constructor or methods
- Makes a method static
- Prevents inheritance
The super keyword is used to call functions on an object's parent class.
In a constructor, super calls the parent constructor and must be called before accessing this.
In methods, super accesses parent class methods.
You must call super in the child constructor before using this, or you will get an error.
Super provides clean syntax for inheritance compared to older patterns.
It is essential for extending classes in ES6.
Correct Answer: Calls the parent class constructor or methods
Example Code
// Parent class
class Animal {
constructor(name) {
this.name = name;
}
eat() {
console.log(this.name + ' is eating');
}
makeSound() {
console.log('Some generic sound');
}
}
// Child class
class Dog extends Animal {
constructor(name, breed) {
super(name); // Call parent constructor
this.breed = breed;
}
bark() {
console.log(this.name + ' says woof!');
}
makeSound() {
super.makeSound(); // Call parent method
console.log('Woof woof!');
}
}
const buddy = new Dog('Buddy', 'Golden Retriever');
buddy.eat(); // 'Buddy is eating' (inherited)
buddy.bark(); // 'Buddy says woof!' (own method)
buddy.makeSound();
// 'Some generic sound'
// 'Woof woof!'
// Must call super before this
class Cat extends Animal {
constructor(name, color) {
// this.color = color; // ReferenceError!
super(name); // Must call super first
this.color = color; // Now this is ok
}
}
// Static methods and super
class Parent {
static info() {
console.log('Parent info');
}
}
class Child extends Parent {
static info() {
super.info(); // Call parent static method
console.log('Child info');
}
}
Child.info();
// 'Parent info'
// 'Child info'
// Prototype chain with classes
const dog = new Dog('Max', 'Labrador');
// dog -> Dog.prototype -> Animal.prototype -> Object.prototype -> null
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
console.log(dog instanceof Object); // true133. What is the difference between hasOwnProperty and the in operator?
Difficulty: EasyType: MCQTopic: Object Props
- They are the same
- hasOwnProperty checks own properties, in checks prototype chain too
- in is faster than hasOwnProperty
- hasOwnProperty works with arrays, in doesn't
HasOwnProperty checks if a property exists directly on the object, not in its prototype chain.
The in operator checks if a property exists anywhere in the prototype chain, including inherited properties.
Use hasOwnProperty when you only want own properties, not inherited ones.
This is important when iterating over objects to avoid processing inherited properties.
Modern alternative is Object.hasOwn for better safety.
Understanding this difference is crucial for working with prototypes.
Correct Answer: hasOwnProperty checks own properties, in checks prototype chain too
Example Code
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
console.log('Hello');
};
const john = new Person('John');
john.age = 30;
// hasOwnProperty - only own properties
console.log(john.hasOwnProperty('name')); // true (own)
console.log(john.hasOwnProperty('age')); // true (own)
console.log(john.hasOwnProperty('greet')); // false (inherited)
// in operator - checks prototype chain
console.log('name' in john); // true (own)
console.log('age' in john); // true (own)
console.log('greet' in john); // true (inherited)
console.log('toString' in john); // true (from Object.prototype)
// Iterating safely with for...in
for (let key in john) {
console.log(key); // name, age, greet (includes prototype)
}
for (let key in john) {
if (john.hasOwnProperty(key)) {
console.log(key); // name, age (only own)
}
}
// Modern alternative - Object.keys (only own enumerable)
const ownKeys = Object.keys(john);
console.log(ownKeys); // ['name', 'age']
// Object.hasOwn (ES2022) - safer than hasOwnProperty
console.log(Object.hasOwn(john, 'name')); // true
console.log(Object.hasOwn(john, 'greet')); // false
// Why Object.hasOwn is better
const obj = Object.create(null); // No prototype
// obj.hasOwnProperty('x'); // TypeError!
Object.hasOwn(obj, 'x'); // false (safe)
// Practical example - checking property
function getValue(obj, key) {
if (obj.hasOwnProperty(key)) {
return obj[key];
}
return 'Property not found';
}134. What are getters and setters in JavaScript classes?
Difficulty: MediumType: MCQTopic: Object Props
- Methods that get and set values
- Special methods that act like properties with get/set keywords
- Private properties
- Static methods
Getters and setters are special methods that look like properties but execute code when accessed or assigned.
Getters use the get keyword and are called when you read a property. They must return a value.
Setters use the set keyword and are called when you assign to a property. They receive the new value as a parameter.
They allow computed properties and validation without exposing the implementation.
Getters and setters make APIs cleaner by hiding internal logic.
They are commonly used for data validation, computed properties, and encapsulation.
Correct Answer: Special methods that act like properties with get/set keywords
Example Code
// Getters and setters in class
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
// Getter - acts like property
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
// Setter - acts like property assignment
set fullName(value) {
const parts = value.split(' ');
this.firstName = parts[0];
this.lastName = parts[1];
}
get age() {
return this._age;
}
set age(value) {
if (value < 0) {
throw new Error('Age cannot be negative');
}
this._age = value;
}
}
const john = new Person('John', 'Doe');
// Use getter like a property (no parentheses)
console.log(john.fullName); // 'John Doe'
// Use setter like property assignment
john.fullName = 'Jane Smith';
console.log(john.firstName); // 'Jane'
console.log(john.lastName); // 'Smith'
// Validation with setter
john.age = 30; // Works
console.log(john.age); // 30
// john.age = -5; // Error: Age cannot be negative
// Getters and setters in object literals
const circle = {
_radius: 5,
get radius() {
return this._radius;
},
set radius(value) {
if (value <= 0) {
throw new Error('Radius must be positive');
}
this._radius = value;
},
get area() {
return Math.PI * this._radius ** 2;
},
get circumference() {
return 2 * Math.PI * this._radius;
}
};
console.log(circle.radius); // 5
console.log(circle.area); // 78.53...
circle.radius = 10;
console.log(circle.area); // 314.15... (computed automatically)
// Private-like properties
class BankAccount {
constructor(balance) {
this._balance = balance; // Convention: _ means private
}
get balance() {
return this._balance;
}
set balance(value) {
throw new Error('Cannot set balance directly');
}
deposit(amount) {
this._balance += amount;
}
}
const account = new BankAccount(1000);
console.log(account.balance); // 1000
// account.balance = 5000; // Error!135. What are static methods in JavaScript classes?
Difficulty: EasyType: MCQTopic: JS Classes
- Methods that never change
- Methods called on the class itself, not instances
- Methods that cannot be inherited
- Private methods
Static methods belong to the class itself, not to instances of the class.
You call static methods directly on the class, not on objects created from the class.
Static methods cannot access instance properties using this, because they are not called on instances.
Static methods are useful for utility functions related to the class.
Common examples include factory methods, helper functions, and utility operations.
They are defined using the static keyword in classes.
Correct Answer: Methods called on the class itself, not instances
Example Code
class MathUtils {
// Static method
static add(a, b) {
return a + b;
}
static multiply(a, b) {
return a * b;
}
// Instance method
calculate(x, y) {
return x + y;
}
}
// Call static method on class
console.log(MathUtils.add(5, 3)); // 8
console.log(MathUtils.multiply(4, 2)); // 8
// Cannot call on instance
const math = new MathUtils();
// math.add(5, 3); // TypeError!
// Can call instance method
console.log(math.calculate(5, 3)); // 8
// Factory pattern with static method
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
static create(name, email) {
// Validation or processing
if (!email.includes('@')) {
throw new Error('Invalid email');
}
return new User(name, email);
}
static fromJSON(json) {
const data = JSON.parse(json);
return new User(data.name, data.email);
}
}
const user1 = User.create('John', 'john@example.com');
const user2 = User.fromJSON('{"name":"Jane","email":"jane@example.com"}');
// Built-in static methods
console.log(Array.isArray([1, 2, 3])); // true (Array.isArray)
console.log(Object.keys({a: 1})); // ['a'] (Object.keys)
// Static properties (ES2022)
class Config {
static apiUrl = 'https://api.example.com';
static timeout = 5000;
}
console.log(Config.apiUrl); // 'https://api.example.com'
// Inheritance and static methods
class Animal {
static type() {
return 'Animal';
}
}
class Dog extends Animal {
static type() {
return 'Dog';
}
}
console.log(Animal.type()); // 'Animal'
console.log(Dog.type()); // 'Dog'136. Explain prototypal inheritance in JavaScript. How does it differ from classical inheritance?
Difficulty: HardType: SubjectiveTopic: JS Prototypes
JavaScript uses prototypal inheritance, which is fundamentally different from classical inheritance used in languages like Java or C++.
Prototypal inheritance:
Objects inherit directly from other objects, not from classes. Every object has a hidden link to another object called its prototype. When you access a property, JavaScript looks in the object first, then follows the prototype chain until found or reaching null.
How it works:
Each object has an internal prototype property accessible via underscore underscore proto underscore or Object.getPrototypeOf. Functions have a prototype property used when creating objects with new. New objects created from a constructor get that constructor's prototype as their prototype. Properties and methods can be shared through the prototype chain.
Classical inheritance:
Classes are blueprints for objects. Objects are instances of classes. Inheritance creates is-a relationships. Classes inherit from other classes using extends keyword.
Key differences:
JavaScript has no true classes, only objects. ES6 classes are syntactic sugar over prototypes. In classical OOP, you define classes then create instances. In prototypal OOP, you create objects that serve as prototypes for other objects. Prototypal inheritance is more flexible but can be confusing.
Advantages of prototypal inheritance:
More flexible and dynamic. Can change prototypes at runtime. Memory efficient by sharing methods. Simpler mental model, everything is an object.
Disadvantages:
Can be confusing for developers from classical OOP backgrounds. Easier to make mistakes with prototype chain. Less structure than class-based systems.
Best practices:
Use ES6 classes for cleaner syntax. Understand prototypes underneath. Share methods through prototype, not in constructor. Avoid modifying built-in prototypes.
Understanding prototypal inheritance is essential for mastering JavaScript and common in interviews.
Example Code
// PROTOTYPAL INHERITANCE
// Objects inherit from objects
const animalProto = {
eat: function() {
console.log(this.name + ' is eating');
}
};
const dog = Object.create(animalProto);
dog.name = 'Buddy';
dog.bark = function() {
console.log(this.name + ' says woof!');
};
dog.eat(); // 'Buddy is eating' (inherited)
dog.bark(); // 'Buddy says woof!' (own)
// Prototype chain
console.log(Object.getPrototypeOf(dog) === animalProto); // true
// CONSTRUCTOR FUNCTION PATTERN
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(this.name + ' is eating');
};
function Dog(name, breed) {
Animal.call(this, name); // Call parent constructor
this.breed = breed;
}
// Set up inheritance
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log(this.name + ' says woof!');
};
const buddy = new Dog('Buddy', 'Golden Retriever');
buddy.eat(); // 'Buddy is eating'
buddy.bark(); // 'Buddy says woof!'
// Prototype chain: buddy -> Dog.prototype -> Animal.prototype -> Object.prototype -> null
// ES6 CLASS SYNTAX (still uses prototypes)
class AnimalClass {
constructor(name) {
this.name = name;
}
eat() {
console.log(this.name + ' is eating');
}
}
class DogClass extends AnimalClass {
constructor(name, breed) {
super(name);
this.breed = breed;
}
bark() {
console.log(this.name + ' says woof!');
}
}
const max = new DogClass('Max', 'Labrador');
max.eat(); // 'Max is eating'
max.bark(); // 'Max says woof!'
// Still using prototypes underneath
console.log(max.__proto__ === DogClass.prototype); // true
console.log(DogClass.prototype.__proto__ === AnimalClass.prototype); // true
// CLASSICAL INHERITANCE (for comparison)
// In Java:
// class Animal {
// String name;
// void eat() { ... }
// }
// class Dog extends Animal {
// String breed;
// void bark() { ... }
// }
// Dog dog = new Dog();
// Key differences:
// 1. JavaScript: Objects inherit from objects
// Classical: Classes inherit from classes
// 2. JavaScript: Dynamic prototype chain
// Classical: Fixed class hierarchy
// 3. JavaScript: Can modify prototypes at runtime
// Classical: Cannot modify classes after definition
// Memory efficiency
function Person(name) {
this.name = name;
// Bad: Each instance gets its own copy
// this.greet = function() { console.log('Hi'); };
}
// Good: Shared across all instances
Person.prototype.greet = function() {
console.log('Hi, I am ' + this.name);
};
const p1 = new Person('John');
const p2 = new Person('Jane');
// Same method reference (memory efficient)
console.log(p1.greet === p2.greet); // true137. Compare constructor functions and ES6 classes. What are the advantages of each?
Difficulty: MediumType: SubjectiveTopic: JS Classes
Constructor functions and ES6 classes both create objects with shared methods, but have important differences.
Constructor functions:
Traditional pre-ES6 way to create object types. Just regular functions called with new keyword. Manually add methods to prototype. More verbose but shows how JavaScript works. Greater flexibility in some cases.
ES6 classes:
Modern syntax introduced in ES6. Syntactic sugar over constructor functions and prototypes. Cleaner and more familiar syntax for developers from other languages. Built-in support for inheritance with extends and super. Methods are non-enumerable by default. Must be called with new, cannot call as regular function.
Key differences:
Syntax: Classes are cleaner and more readable. Strict mode: Classes automatically run in strict mode. Hoisting: Functions are hoisted, classes are not. Calling: Functions can be called without new, classes cannot. Method enumeration: Class methods are non-enumerable, function prototype methods are enumerable.
Advantages of constructor functions:
More explicit about prototypes. Can be called without new with checks. More flexibility in dynamic scenarios. Better for understanding JavaScript internals.
Advantages of classes:
Cleaner, more readable syntax. Better for developers from classical OOP languages. Built-in inheritance support. Less boilerplate for common patterns. Industry standard in modern JavaScript.
When to use:
Use classes in modern code for better readability. Use constructor functions when working with legacy code. Understand both because classes use prototypes underneath.
Both approaches ultimately create the same prototype-based structure.
Example Code
// CONSTRUCTOR FUNCTION
function PersonFunc(name, age) {
this.name = name;
this.age = age;
}
PersonFunc.prototype.greet = function() {
console.log('Hi, I am ' + this.name);
};
PersonFunc.prototype.getAge = function() {
return this.age;
};
// Static method
PersonFunc.create = function(name, age) {
return new PersonFunc(name, age);
};
const john = new PersonFunc('John', 30);
john.greet(); // 'Hi, I am John'
// ES6 CLASS
class PersonClass {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hi, I am ${this.name}`);
}
getAge() {
return this.age;
}
static create(name, age) {
return new PersonClass(name, age);
}
}
const jane = new PersonClass('Jane', 25);
jane.greet(); // 'Hi, I am Jane'
// DIFFERENCE 1: Enumeration
// Constructor function methods are enumerable
for (let key in john) {
console.log(key); // name, age, greet, getAge
}
// Class methods are non-enumerable
for (let key in jane) {
console.log(key); // name, age (only own properties)
}
// DIFFERENCE 2: Calling without new
// Function can be called without new (mistakes possible)
const wrong = PersonFunc('Bob', 35); // Forgot new
console.log(wrong); // undefined
// console.log(window.name); // 'Bob' (polluted global!)
// Class must use new
// const wrong2 = PersonClass('Alice', 28); // TypeError!
// DIFFERENCE 3: Hoisting
const p1 = new PersonFunc('Test', 20); // Works (hoisted)
// const p2 = new PersonClass('Test', 20); // ReferenceError!
// (classes not hoisted)
// INHERITANCE COMPARISON
// Constructor function inheritance (verbose)
function AnimalFunc(name) {
this.name = name;
}
AnimalFunc.prototype.eat = function() {
console.log(this.name + ' is eating');
};
function DogFunc(name, breed) {
AnimalFunc.call(this, name);
this.breed = breed;
}
DogFunc.prototype = Object.create(AnimalFunc.prototype);
DogFunc.prototype.constructor = DogFunc;
DogFunc.prototype.bark = function() {
console.log(this.name + ' barks');
};
// Class inheritance (clean)
class AnimalClass {
constructor(name) {
this.name = name;
}
eat() {
console.log(this.name + ' is eating');
}
}
class DogClass extends AnimalClass {
constructor(name, breed) {
super(name);
this.breed = breed;
}
bark() {
console.log(this.name + ' barks');
}
}
// WHEN TO USE WHAT
// Use classes (modern, preferred)
class User {
constructor(name) {
this.name = name;
}
greet() {
console.log('Hello ' + this.name);
}
}
// Use constructor functions (legacy, or when needed)
function OldStyleUser(name) {
this.name = name;
}
OldStyleUser.prototype.greet = function() {
console.log('Hello ' + this.name);
};
// Both create same structure
console.log(typeof PersonClass); // 'function'
console.log(PersonClass.prototype.greet); // function
console.log(PersonFunc.prototype.greet); // function138. Explain the prototype chain in JavaScript. How does property lookup work?
Difficulty: HardType: SubjectiveTopic: JS Prototypes
The prototype chain is the mechanism JavaScript uses to implement inheritance and property lookup.
How it works:
Every object has an internal prototype link that points to another object. This creates a chain of objects. When you access a property, JavaScript searches up this chain until found or reaching null. Null marks the end of the chain.
Property lookup process:
Step 1: Check if property exists directly on the object.
Step 2: If not found, check the object's prototype.
Step 3: If not found, check the prototype's prototype.
Step 4: Continue until property is found or reaching null.
Step 5: If null is reached, return undefined.
The chain structure:
All objects ultimately inherit from Object.prototype. Arrays inherit from Array.prototype which inherits from Object.prototype. Functions inherit from Function.prototype which inherits from Object.prototype. The chain always ends with Object.prototype whose prototype is null.
Methods on the prototype:
Methods shared by all instances live on the prototype. This saves memory compared to copying methods to each instance. Changes to prototype affect all existing instances.
Common gotchas:
Adding properties to an object does not affect its prototype. Modifying prototype affects all objects using that prototype. Performance decreases with longer chains. Can accidentally overwrite prototype methods.
Accessing prototypes:
Use Object.getPrototypeOf for reading. Use Object.setPrototypeOf for setting, though this is slow. Use underscore underscore proto underscore for convenience, though deprecated. Use instanceof to check prototype chain.
Understanding the prototype chain is fundamental to JavaScript and frequently tested in interviews.
Example Code
// BASIC PROTOTYPE CHAIN
const obj = {};
// obj -> Object.prototype -> null
console.log(Object.getPrototypeOf(obj)); // Object.prototype
console.log(Object.getPrototypeOf(Object.prototype)); // null
// PROPERTY LOOKUP DEMONSTRATION
function Person(name) {
this.name = name; // Own property
}
Person.prototype.greet = function() {
console.log('Hello ' + this.name);
}; // Prototype property
const john = new Person('John');
// Lookup for 'name'
// 1. Check john object - FOUND
console.log(john.name); // 'John'
// Lookup for 'greet'
// 1. Check john object - not found
// 2. Check Person.prototype - FOUND
john.greet(); // 'Hello John'
// Lookup for 'toString'
// 1. Check john object - not found
// 2. Check Person.prototype - not found
// 3. Check Object.prototype - FOUND
console.log(john.toString()); // '[object Object]'
// Lookup for 'nonExistent'
// 1. Check john object - not found
// 2. Check Person.prototype - not found
// 3. Check Object.prototype - not found
// 4. Reach null - return undefined
console.log(john.nonExistent); // undefined
// VISUALIZING THE CHAIN
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log('Eating');
};
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log('Woof!');
};
const buddy = new Dog('Buddy', 'Labrador');
// Prototype chain:
// buddy -> Dog.prototype -> Animal.prototype -> Object.prototype -> null
// Verify chain
console.log(Object.getPrototypeOf(buddy) === Dog.prototype); // true
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype); // true
console.log(Object.getPrototypeOf(Animal.prototype) === Object.prototype); // true
console.log(Object.getPrototypeOf(Object.prototype)); // null
// Property lookup on buddy
buddy.bark(); // Found on Dog.prototype
buddy.eat(); // Found on Animal.prototype
console.log(buddy.toString()); // Found on Object.prototype
// SHADOWING PROPERTIES
const parent = {
value: 10
};
const child = Object.create(parent);
console.log(child.value); // 10 (from parent)
child.value = 20; // Creates own property (shadows parent)
console.log(child.value); // 20 (own property)
console.log(parent.value); // 10 (unchanged)
delete child.value; // Remove own property
console.log(child.value); // 10 (back to parent)
// PERFORMANCE IMPLICATIONS
// Short chain (fast)
const obj1 = {x: 1};
console.log(obj1.x); // Quick lookup
// Long chain (slower)
const level1 = {a: 1};
const level2 = Object.create(level1);
const level3 = Object.create(level2);
const level4 = Object.create(level3);
const level5 = Object.create(level4);
console.log(level5.a); // Slower (walks through 4 prototypes)
// COMMON METHODS ON OBJECT.PROTOTYPE
const anyObj = {};
// These are all inherited from Object.prototype
console.log(anyObj.toString()); // '[object Object]'
console.log(anyObj.hasOwnProperty('x')); // false
console.log(anyObj.valueOf()); // {}
// CHECKING THE CHAIN
console.log(buddy instanceof Dog); // true
console.log(buddy instanceof Animal); // true
console.log(buddy instanceof Object); // true
// Using in operator (checks entire chain)
console.log('bark' in buddy); // true
console.log('eat' in buddy); // true
// Using hasOwnProperty (only own properties)
console.log(buddy.hasOwnProperty('name')); // true
console.log(buddy.hasOwnProperty('bark')); // false
console.log(buddy.hasOwnProperty('eat')); // false139. What is Object.create() and how is it used for inheritance? Compare it with constructor functions.
Difficulty: MediumType: SubjectiveTopic: JS Prototypes
Object.create is a method that creates a new object with a specified prototype object. It provides direct control over an object's prototype without using constructor functions.
How Object.create works:
Takes an object as first argument. Creates a new empty object. Sets the new object's prototype to the argument. Returns the new object. Optional second argument specifies property descriptors.
Use cases:
Creating objects with specific prototypes. Setting up inheritance without constructors. Creating objects without Object.prototype, using null. Implementing object composition patterns.
Inheritance with Object.create:
Create a prototype object with shared methods. Use Object.create to create child objects. Add instance-specific properties after creation. More explicit than constructor functions.
Comparing to constructor functions:
Object.create is more direct and explicit. Constructor functions provide encapsulation with private variables. Object.create better for simple prototypal patterns. Constructor functions better for creating multiple similar objects. Classes provide best syntax for most cases.
Advantages of Object.create:
No need for constructor function. Direct prototype manipulation. Can create objects without Object.prototype. More functional programming style. No confusion about new keyword.
Disadvantages:
Less encapsulation than constructors. No automatic property initialization. More verbose for creating multiple objects. Less familiar to developers from classical OOP.
Best practices:
Use Object.create for simple inheritance. Use classes for complex object hierarchies. Use Object.create with null for dictionary objects. Understand it helps understand prototypes.
Object.create is fundamental to understanding JavaScript's prototype system.
Example Code
// BASIC OBJECT.CREATE
const personProto = {
greet: function() {
console.log('Hi, I am ' + this.name);
},
introduce: function() {
console.log(`${this.name}, age ${this.age}`);
}
};
// Create object with personProto as prototype
const john = Object.create(personProto);
john.name = 'John';
john.age = 30;
john.greet(); // 'Hi, I am John'
// john inherits from personProto
console.log(Object.getPrototypeOf(john) === personProto); // true
// INHERITANCE PATTERN
const animal = {
eat: function() {
console.log(this.name + ' is eating');
},
sleep: function() {
console.log(this.name + ' is sleeping');
}
};
const dog = Object.create(animal);
dog.name = 'Buddy';
dog.bark = function() {
console.log(this.name + ' says woof!');
};
dog.eat(); // 'Buddy is eating' (inherited)
dog.bark(); // 'Buddy says woof!' (own)
// Multi-level inheritance
const puppy = Object.create(dog);
puppy.name = 'Max';
puppy.play = function() {
console.log(this.name + ' is playing');
};
puppy.play(); // 'Max is playing' (own)
puppy.bark(); // 'Max says woof!' (from dog)
puppy.eat(); // 'Max is eating' (from animal)
// PROPERTY DESCRIPTORS
const person = Object.create(personProto, {
name: {
value: 'Jane',
writable: true,
enumerable: true,
configurable: true
},
age: {
value: 25,
writable: true,
enumerable: true
},
id: {
value: 123,
writable: false, // Read-only
enumerable: false // Won't show in for...in
}
});
console.log(person.name); // 'Jane'
console.log(person.id); // 123
// person.id = 456; // Cannot change (writable: false)
// CREATING OBJECT WITHOUT PROTOTYPE
const dictionary = Object.create(null);
dictionary.key1 = 'value1';
dictionary.key2 = 'value2';
// No inherited properties
console.log(dictionary.toString); // undefined
console.log(dictionary.hasOwnProperty); // undefined
// Useful for pure data storage
console.log(Object.getPrototypeOf(dictionary)); // null
// COMPARE: OBJECT.CREATE VS CONSTRUCTOR
// Using Object.create
const carProto = {
start: function() {
console.log(this.model + ' starting');
}
};
const car1 = Object.create(carProto);
car1.model = 'Tesla';
car1.year = 2024;
// Using constructor function
function Car(model, year) {
this.model = model;
this.year = year;
}
Car.prototype.start = function() {
console.log(this.model + ' starting');
};
const car2 = new Car('Tesla', 2024);
// Both create similar structure
car1.start(); // 'Tesla starting'
car2.start(); // 'Tesla starting'
// INHERITANCE COMPARISON
// Object.create approach
function createDog(name, breed) {
const dog = Object.create(animal);
dog.name = name;
dog.breed = breed;
dog.bark = function() {
console.log('Woof!');
};
return dog;
}
const buddy = createDog('Buddy', 'Labrador');
// Constructor approach
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(this.name + ' is eating');
};
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log('Woof!');
};
const max = new Dog('Max', 'Retriever');
// WHEN TO USE WHAT
// Use Object.create for:
// - Simple prototypal inheritance
// - Creating objects with specific prototypes
// - Dictionary objects (with null prototype)
// Use constructor functions for:
// - Creating multiple similar objects
// - Encapsulation with closures
// - When working with legacy code
// Use classes for:
// - Modern, clean syntax
// - Complex inheritance hierarchies
// - New projects140. What does the call() method do in JavaScript?
Difficulty: MediumType: MCQTopic: this Binding
- Calls a function repeatedly
- Calls a function with a specified this value and arguments
- Creates a copy of a function
- Delays function execution
The call method invokes a function with a specific this value and individual arguments.
First parameter is the object to use as this inside the function. Remaining parameters are passed as individual arguments to the function.
Call executes the function immediately.
It is useful for function borrowing, where you use a method from one object on another object.
Call is commonly used to invoke parent constructors in inheritance.
Understanding call is essential for controlling this in JavaScript.
Correct Answer: Calls a function with a specified this value and arguments
Example Code
// Basic call usage
function greet(greeting, punctuation) {
console.log(greeting + ', ' + this.name + punctuation);
}
const person = { name: 'John' };
// Call greet with person as this
greet.call(person, 'Hello', '!'); // 'Hello, John!'
// Without call, this would be undefined or window
greet('Hello', '!'); // 'Hello, undefined!' (this not set)
// Function borrowing
const person1 = {
name: 'John',
greet: function() {
console.log('Hi, I am ' + this.name);
}
};
const person2 = { name: 'Jane' };
// Borrow greet method from person1
person1.greet.call(person2); // 'Hi, I am Jane'
// Array-like objects
function sum() {
// arguments is array-like, not real array
const args = Array.prototype.slice.call(arguments);
return args.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
// Constructor chaining
function Animal(name) {
this.name = name;
}
function Dog(name, breed) {
Animal.call(this, name); // Call parent constructor
this.breed = breed;
}
const buddy = new Dog('Buddy', 'Labrador');
console.log(buddy.name); // 'Buddy'
console.log(buddy.breed); // 'Labrador'141. What is the difference between call() and apply()?
Difficulty: MediumType: MCQTopic: this Binding
- call is faster than apply
- call takes individual arguments, apply takes an array
- apply can only be used with arrays
- There is no difference
Call and apply both invoke functions with a specified this value, but differ in how they accept arguments.
Call takes individual arguments separated by commas. First argument is this, rest are function parameters.
Apply takes an array of arguments. First argument is this, second is an array containing all function parameters.
Both execute the function immediately.
Use call when you know arguments individually. Use apply when you have arguments in an array.
Modern JavaScript often uses spread operator instead of apply.
Remember: Call is comma separated, Apply takes an array.
Correct Answer: call takes individual arguments, apply takes an array
Example Code
function introduce(greeting, profession) {
console.log(greeting + ', I am ' + this.name + ', a ' + profession);
}
const person = { name: 'John' };
// call - individual arguments
introduce.call(person, 'Hello', 'Developer');
// 'Hello, I am John, a Developer'
// apply - array of arguments
introduce.apply(person, ['Hello', 'Developer']);
// 'Hello, I am John, a Developer'
// Useful when you already have array
const args = ['Hi', 'Engineer'];
introduce.apply(person, args);
// Math.max with apply (classic use case)
const numbers = [5, 6, 2, 3, 7, 1];
// apply spreads array as individual arguments
const max = Math.max.apply(null, numbers);
console.log(max); // 7
// Modern alternative with spread operator
const max2 = Math.max(...numbers);
console.log(max2); // 7
// call with spread (modern approach)
introduce.call(person, ...args);
// Choosing between call and apply
function sum(a, b, c) {
return a + b + c;
}
// If you have values separately
const result1 = sum.call(null, 1, 2, 3); // 6
// If you have values in array
const nums = [1, 2, 3];
const result2 = sum.apply(null, nums); // 6
// Memory trick:
// call: c for comma
// apply: a for array142. What does the bind() method return?
Difficulty: MediumType: MCQTopic: this Binding
- Executes the function immediately
- Returns a new function with bound this value
- Returns the original function
- Returns undefined
Bind creates a new function with a permanently bound this value. Unlike call and apply, bind does not execute the function immediately.
First parameter sets the this value. Additional parameters are pre-set as arguments.
The returned function can be called later, and this will always be the bound value.
Bind is useful for event handlers, callbacks, and partial application.
You can only bind this once. Calling bind again on a bound function does nothing.
Bind is essential for maintaining context in callbacks and React components.
Correct Answer: Returns a new function with bound this value
Example Code
const person = {
name: 'John',
greet: function() {
console.log('Hi, I am ' + this.name);
}
};
// bind creates new function
const greetFunc = person.greet.bind(person);
// Call later, this is still person
greetFunc(); // 'Hi, I am John'
// Without bind, this is lost
const greetLost = person.greet;
greetLost(); // 'Hi, I am undefined' (this is window or undefined)
// Event handler example
const button = document.getElementById('btn');
const user = {
name: 'Alice',
handleClick: function() {
console.log(this.name + ' clicked');
}
};
// Wrong: this will be button element
// button.addEventListener('click', user.handleClick);
// Right: bind this to user
button.addEventListener('click', user.handleClick.bind(user));
// Now clicking shows 'Alice clicked'
// Partial application with bind
function multiply(a, b) {
return a * b;
}
// Pre-set first argument
const double = multiply.bind(null, 2);
console.log(double(5)); // 10 (2 * 5)
console.log(double(7)); // 14 (2 * 7)
const triple = multiply.bind(null, 3);
console.log(triple(4)); // 12 (3 * 4)
// setTimeout example
const obj = {
value: 42,
printValue: function() {
console.log(this.value);
}
};
setTimeout(obj.printValue, 1000); // undefined (lost this)
setTimeout(obj.printValue.bind(obj), 1000); // 42 (preserved this)
// Cannot rebind
const bound = greetFunc.bind({ name: 'Jane' });
bound(); // Still 'Hi, I am John' (first bind wins)143. What is currying in JavaScript?
Difficulty: HardType: MCQTopic: Functional JS
- Adding spices to functions
- Transforming a function with multiple arguments into nested single-argument functions
- Making functions run faster
- Converting callbacks to promises
Currying transforms a function that takes multiple arguments into a sequence of functions that each take a single argument.
Instead of calling function with all arguments at once, you call it with one argument, which returns another function that takes the next argument, and so on.
Currying enables partial application and function composition.
It makes functions more reusable and flexible.
Currying is common in functional programming.
You can curry functions manually or use libraries like Lodash.
Correct Answer: Transforming a function with multiple arguments into nested single-argument functions
Example Code
// Regular function
function add(a, b, c) {
return a + b + c;
}
console.log(add(1, 2, 3)); // 6
// Curried version
function addCurried(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
console.log(addCurried(1)(2)(3)); // 6
// Arrow function currying (cleaner)
const addArrow = a => b => c => a + b + c;
console.log(addArrow(1)(2)(3)); // 6
// Partial application with currying
const add5 = addArrow(5);
console.log(add5(3)(2)); // 10
const add5and3 = add5(3);
console.log(add5and3(7)); // 15
// Practical example - greeting
const greet = greeting => name => time => {
return `${greeting} ${name}, good ${time}!`;
};
const sayHello = greet('Hello');
const sayHelloToJohn = sayHello('John');
console.log(sayHelloToJohn('morning')); // 'Hello John, good morning!'
console.log(sayHelloToJohn('evening')); // 'Hello John, good evening!'
// Reusable specialized functions
const greetFormally = greet('Good day');
const greetCasually = greet('Hey');
console.log(greetFormally('Sir')('afternoon'));
console.log(greetCasually('Buddy')('night'));
// Generic curry function
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return function(...nextArgs) {
return curried.apply(this, args.concat(nextArgs));
};
};
}
// Use curry helper
function multiply(a, b, c) {
return a * b * c;
}
const curriedMultiply = curry(multiply);
console.log(curriedMultiply(2)(3)(4)); // 24
console.log(curriedMultiply(2, 3)(4)); // 24
console.log(curriedMultiply(2)(3, 4)); // 24
console.log(curriedMultiply(2, 3, 4)); // 24144. What is an IIFE (Immediately Invoked Function Expression)?
Difficulty: MediumType: MCQTopic: JS Modules
- A function that calls itself
- A function that executes immediately after being defined
- A function that runs on page load
- A recursive function
An IIFE is a function that runs immediately after it is defined. It is wrapped in parentheses and followed by another set of parentheses to invoke it.
IIFEs create a new scope, preventing variables from polluting the global scope.
They were commonly used before ES6 modules for encapsulation.
IIFEs can return values or objects.
The pattern is useful for initialization code that runs once.
Modern JavaScript uses modules instead, but IIFEs still appear in legacy code and are good interview questions.
Correct Answer: A function that executes immediately after being defined
Example Code
// Basic IIFE syntax
(function() {
console.log('I run immediately!');
})();
// Arrow function IIFE
(() => {
console.log('Arrow IIFE');
})();
// IIFE with parameters
(function(name) {
console.log('Hello ' + name);
})('John');
// IIFE returning a value
const result = (function() {
return 2 + 2;
})();
console.log(result); // 4
// Creating private variables
const counter = (function() {
let count = 0; // Private variable
return {
increment: function() {
count++;
return count;
},
decrement: function() {
count--;
return count;
},
getCount: function() {
return count;
}
};
})();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount()); // 2
// console.log(counter.count); // undefined (private!)
// Module pattern with IIFE
const myModule = (function() {
// Private
const privateVar = 'I am private';
function privateFunction() {
console.log(privateVar);
}
// Public API
return {
publicMethod: function() {
privateFunction();
},
publicVar: 'I am public'
};
})();
myModule.publicMethod(); // 'I am private'
console.log(myModule.publicVar); // 'I am public'
// console.log(myModule.privateVar); // undefined
// Avoid global pollution
(function() {
var temp = 'Only exists in this scope';
// Do something
})();
// console.log(temp); // ReferenceError
// Classic var loop fix
for (var i = 0; i < 3; i++) {
(function(index) {
setTimeout(function() {
console.log(index); // 0, 1, 2
}, 100);
})(i);
}145. What does it mean that JavaScript has first-class functions?
Difficulty: EasyType: MCQTopic: Functional JS
- Functions run faster than other code
- Functions can be treated like any other value
- Functions are always defined first
- Functions have the highest priority
First-class functions mean functions are treated as first-class citizens, just like any other value.
You can assign functions to variables. Pass functions as arguments to other functions. Return functions from functions. Store functions in data structures.
This enables higher-order functions, callbacks, closures, and functional programming patterns.
First-class functions are fundamental to JavaScript's flexibility.
Many JavaScript patterns depend on this feature.
Understanding this concept is essential for advanced JavaScript.
Correct Answer: Functions can be treated like any other value
Example Code
// 1. Assign function to variable
const greet = function(name) {
return 'Hello ' + name;
};
console.log(greet('John')); // 'Hello John'
// 2. Pass function as argument
function executeFunction(fn, value) {
return fn(value);
}
const result = executeFunction(greet, 'Alice');
console.log(result); // 'Hello Alice'
// 3. Return function from function
function createMultiplier(multiplier) {
return function(number) {
return number * multiplier;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
// 4. Store functions in array
const operations = [
function(a, b) { return a + b; },
function(a, b) { return a - b; },
function(a, b) { return a * b; }
];
console.log(operations[0](5, 3)); // 8
console.log(operations[1](5, 3)); // 2
console.log(operations[2](5, 3)); // 15
// 5. Store functions in object
const calculator = {
add: function(a, b) { return a + b; },
subtract: function(a, b) { return a - b; },
multiply: function(a, b) { return a * b; }
};
console.log(calculator.add(10, 5)); // 15
// Array methods use first-class functions
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(function(n) {
return n * 2;
});
console.log(doubled); // [2, 4, 6, 8, 10]
// Callback pattern
function fetchData(callback) {
setTimeout(function() {
callback('Data loaded');
}, 1000);
}
fetchData(function(data) {
console.log(data); // 'Data loaded'
});146. What is a pure function?
Difficulty: MediumType: MCQTopic: Functional JS
- A function without bugs
- A function that returns the same output for the same input and has no side effects
- A function that only uses primitives
- A function declared with const
A pure function has two requirements:
Same input always produces same output. The function is deterministic and predictable.
No side effects. The function does not modify external state, does not mutate arguments, does not perform I/O, and does not change global variables.
Pure functions are easier to test, debug, and reason about.
They enable functional programming patterns like memoization.
Impure functions have side effects or depend on external state.
Pure functions improve code quality and maintainability.
Correct Answer: A function that returns the same output for the same input and has no side effects
Example Code
// PURE FUNCTIONS
// Pure - same input, same output, no side effects
function add(a, b) {
return a + b;
}
console.log(add(2, 3)); // Always 5
console.log(add(2, 3)); // Always 5
// Pure - doesn't modify input
function double(arr) {
return arr.map(n => n * 2);
}
const nums = [1, 2, 3];
const doubled = double(nums);
console.log(nums); // [1, 2, 3] (unchanged)
console.log(doubled); // [2, 4, 6]
// IMPURE FUNCTIONS
// Impure - modifies external variable
let total = 0;
function addToTotal(value) {
total += value; // Side effect!
return total;
}
console.log(addToTotal(5)); // 5
console.log(addToTotal(5)); // 10 (different output!)
// Impure - modifies argument
function addItem(arr, item) {
arr.push(item); // Mutates input!
return arr;
}
// Impure - depends on external state
function getCurrentTime() {
return new Date(); // Different output each time!
}
// Impure - I/O operation
function logMessage(msg) {
console.log(msg); // Side effect!
}
// Impure - random output
function rollDice() {
return Math.floor(Math.random() * 6) + 1; // Non-deterministic!
}
// MAKING IMPURE FUNCTIONS PURE
// Impure version
function addItemImpure(arr, item) {
arr.push(item);
return arr;
}
// Pure version
function addItemPure(arr, item) {
return [...arr, item]; // Return new array
}
const original = [1, 2, 3];
const newArr = addItemPure(original, 4);
console.log(original); // [1, 2, 3] (unchanged)
console.log(newArr); // [1, 2, 3, 4]
// BENEFITS OF PURE FUNCTIONS
// 1. Easy to test
function multiply(a, b) {
return a * b;
}
console.assert(multiply(2, 3) === 6);
console.assert(multiply(2, 3) === 6); // Always same!
// 2. Memoization (caching)
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 expensiveCalc = memoize((n) => {
console.log('Computing...');
return n * n;
});
console.log(expensiveCalc(5)); // Computing... 25
console.log(expensiveCalc(5)); // 25 (from cache, no computing)147. What is function composition?
Difficulty: HardType: MCQTopic: Functional JS
- Writing functions inside functions
- Combining multiple functions to create a new function
- Converting functions to strings
- Making functions asynchronous
Function composition is combining two or more functions to create a new function where the output of one function becomes the input of the next.
It follows the mathematical concept of composition: f composed with g of x equals f of g of x.
Composition makes code more readable and reusable by breaking complex operations into simple functions.
The compose function typically executes from right to left. Pipe function executes from left to right.
Composition is a core principle of functional programming.
It enables building complex behavior from simple, testable functions.
Correct Answer: Combining multiple functions to create a new function
Example Code
// Simple functions
const add5 = x => x + 5;
const multiply2 = x => x * 2;
const subtract3 = x => x - 3;
// Manual composition
const result = subtract3(multiply2(add5(10)));
console.log(result); // (10 + 5) * 2 - 3 = 27
// Compose function (right to left)
function compose(...fns) {
return function(value) {
return fns.reduceRight((acc, fn) => fn(acc), value);
};
}
const calculate = compose(subtract3, multiply2, add5);
console.log(calculate(10)); // 27
// Pipe function (left to right)
function pipe(...fns) {
return function(value) {
return fns.reduce((acc, fn) => fn(acc), value);
};
}
const calculate2 = pipe(add5, multiply2, subtract3);
console.log(calculate2(10)); // 27 (same result)
// Practical example - data transformation
const users = [
{ name: 'john', age: 25, active: true },
{ name: 'jane', age: 30, active: false },
{ name: 'bob', age: 35, active: true }
];
// Individual functions
const filterActive = users => users.filter(u => u.active);
const mapNames = users => users.map(u => u.name);
const capitalizeNames = names => names.map(n => n.toUpperCase());
// Compose them
const getActiveUserNames = pipe(
filterActive,
mapNames,
capitalizeNames
);
console.log(getActiveUserNames(users)); // ['JOHN', 'BOB']
// String manipulation example
const trim = str => str.trim();
const toLowerCase = str => str.toLowerCase();
const removeSpaces = str => str.replace(/\s+/g, '-');
const slugify = pipe(trim, toLowerCase, removeSpaces);
console.log(slugify(' Hello World ')); // 'hello-world'
// With arrow functions
const double = x => x * 2;
const addTen = x => x + 10;
const square = x => x * x;
const complexCalc = pipe(double, addTen, square);
console.log(complexCalc(5)); // ((5 * 2) + 10) ^ 2 = 400
// Reusable composed functions
const validateEmail = pipe(
email => email.trim(),
email => email.toLowerCase(),
email => email.includes('@')
);
console.log(validateEmail(' Test@Email.com ')); // true148. What is partial application?
Difficulty: MediumType: MCQTopic: Functional JS
- Running part of a function
- Pre-filling some arguments of a function to create a new function
- Using only some methods of an object
- A type of error handling
Partial application creates a new function by pre-filling some arguments of an existing function.
It is different from currying. Currying transforms a multi-argument function into nested single-argument functions. Partial application fixes some arguments and returns a function that takes remaining arguments.
Partial application uses bind or custom functions to fix arguments.
It creates specialized functions from generic ones.
Partial application improves code reusability and readability.
It is commonly used for creating utility functions.
Correct Answer: Pre-filling some arguments of a function to create a new function
Example Code
// Original function
function multiply(a, b, c) {
return a * b * c;
}
// Partial application with bind
const multiplyBy2 = multiply.bind(null, 2);
console.log(multiplyBy2(3, 4)); // 2 * 3 * 4 = 24
const multiplyBy2And3 = multiply.bind(null, 2, 3);
console.log(multiplyBy2And3(4)); // 2 * 3 * 4 = 24
// Custom partial function
function partial(fn, ...fixedArgs) {
return function(...remainingArgs) {
return fn(...fixedArgs, ...remainingArgs);
};
}
const double = partial(multiply, 2, 1);
console.log(double(5)); // 2 * 1 * 5 = 10
// Practical example - logging
function log(level, timestamp, message) {
console.log(`[${level}] ${timestamp}: ${message}`);
}
// Create specialized loggers
const errorLogger = partial(log, 'ERROR');
const infoLogger = partial(log, 'INFO');
errorLogger('2024-01-01', 'Something went wrong');
// [ERROR] 2024-01-01: Something went wrong
infoLogger('2024-01-01', 'App started');
// [INFO] 2024-01-01: App started
// API request example
function fetch(method, url, options) {
console.log(`${method} ${url}`, options);
}
const get = partial(fetch, 'GET');
const post = partial(fetch, 'POST');
const put = partial(fetch, 'PUT');
get('/api/users', { headers: {} });
post('/api/users', { body: {name: 'John'} });
// Difference from currying
// Currying
const curriedMultiply = a => b => c => a * b * c;
console.log(curriedMultiply(2)(3)(4)); // Must call with one arg at a time
// Partial application
const partialMultiply = partial(multiply, 2);
console.log(partialMultiply(3, 4)); // Can call with multiple args
// Combining with composition
const add = (a, b) => a + b;
const addFive = partial(add, 5);
const multiplyByTwo = x => x * 2;
const addFiveAndDouble = pipe(addFive, multiplyByTwo);
console.log(addFiveAndDouble(10)); // (10 + 5) * 2 = 30
// Event handler example
function handleClick(prefix, event) {
console.log(prefix, event.target);
}
const handleButtonClick = partial(handleClick, 'Button clicked:');
// button.addEventListener('click', handleButtonClick);149. What is function borrowing in JavaScript?
Difficulty: EasyType: MCQTopic: this Binding
- Copying a function to another file
- Using a method from one object on another object
- Creating a backup of a function
- Sharing functions between modules
Function borrowing is using a method from one object on another object using call, apply, or bind.
The borrowed method operates on the borrower object as if it were its own method.
Common use case is borrowing array methods for array-like objects.
Function borrowing enables code reuse without inheritance.
It is a powerful technique in JavaScript for leveraging existing methods.
Understanding function borrowing shows mastery of this and context.
Correct Answer: Using a method from one object on another object
Example Code
// Basic function borrowing
const person1 = {
name: 'John',
greet: function() {
console.log('Hello, I am ' + this.name);
}
};
const person2 = { name: 'Jane' };
// Borrow greet from person1
person1.greet.call(person2); // 'Hello, I am Jane'
// Array method borrowing (very common)
function sum() {
// arguments is array-like, not array
const arr = Array.prototype.slice.call(arguments);
return arr.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15
// Modern alternative
function sum2(...args) {
return args.reduce((total, num) => total + num, 0);
}
// Borrowing array methods for NodeList
const divs = document.querySelectorAll('div');
// divs is NodeList, not Array
// Borrow forEach
Array.prototype.forEach.call(divs, function(div) {
console.log(div);
});
// Borrow map
const texts = Array.prototype.map.call(divs, function(div) {
return div.textContent;
});
// Object method borrowing
const obj1 = {
data: [1, 2, 3],
printData: function() {
console.log(this.data);
}
};
const obj2 = { data: [4, 5, 6] };
obj1.printData.call(obj2); // [4, 5, 6]
// Practical example - checking array
function isArray(obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
}
console.log(isArray([1, 2, 3])); // true
console.log(isArray('hello')); // false
// hasOwnProperty borrowing (safe)
const obj = Object.create(null);
// obj.hasOwnProperty('x'); // Error! (no prototype)
const result = Object.prototype.hasOwnProperty.call(obj, 'x');
console.log(result); // false (safe!)
// Borrowing Math methods
const numbers = { 0: 5, 1: 2, 2: 9, length: 3 };
const max = Math.max.apply(null,
Array.prototype.slice.call(numbers, 0, numbers.length)
);
console.log(max); // 9150. Explain call, apply, and bind methods in detail. When would you use each? Provide examples.
Difficulty: HardType: SubjectiveTopic: this Binding
Call, apply, and bind are methods for controlling the this value in function calls. They are essential for managing context in JavaScript.
Call method:
Invokes a function with specified this value and individual arguments. First parameter is this, rest are function arguments. Executes immediately. Use when you know arguments individually.
Apply method:
Invokes a function with specified this value and array of arguments. First parameter is this, second is array. Executes immediately. Use when you have arguments in an array.
Bind method:
Creates a new function with permanently bound this value. Returns new function, does not execute immediately. Can pre-set arguments for partial application. Use for event handlers, callbacks, or creating specialized functions.
Key differences:
Call and apply execute immediately, bind returns new function. Call takes comma-separated arguments, apply takes array. Bind permanently fixes this, call and apply use it for single invocation.
When to use call:
Function borrowing. Constructor chaining in inheritance. When you have individual arguments.
When to use apply:
When arguments are in an array. Using array methods on array-like objects. Math methods with array of numbers.
When to use bind:
Event handlers that need specific this. Callbacks that must maintain context. Partial application. Creating reusable specialized functions. React class component methods.
Common use cases:
Borrowing array methods for arguments or NodeList. Maintaining this in setTimeout or event handlers. Constructor chaining. Converting array-like to array. Function composition and currying.
All three methods are fundamental to JavaScript and appear frequently in interviews.
Example Code
// CALL METHOD
const person = {
name: 'John',
greet: function(greeting, punctuation) {
console.log(greeting + ', ' + this.name + punctuation);
}
};
const anotherPerson = { name: 'Jane' };
// Use call to invoke with different this
person.greet.call(anotherPerson, 'Hello', '!'); // 'Hello, Jane!'
// Constructor chaining
function Animal(name, type) {
this.name = name;
this.type = type;
}
function Dog(name, breed) {
Animal.call(this, name, 'Dog'); // Call parent constructor
this.breed = breed;
}
const buddy = new Dog('Buddy', 'Labrador');
console.log(buddy); // {name: 'Buddy', type: 'Dog', breed: 'Labrador'}
// APPLY METHOD
// Same as call but with array
person.greet.apply(anotherPerson, ['Hi', '...']); // 'Hi, Jane...'
// Math.max with array
const numbers = [5, 6, 2, 3, 7];
const max = Math.max.apply(null, numbers);
console.log(max); // 7
// Modern alternative with spread
const max2 = Math.max(...numbers);
// Converting arguments to array
function toArray() {
return Array.prototype.slice.apply(arguments);
}
const arr = toArray(1, 2, 3, 4);
console.log(arr); // [1, 2, 3, 4]
// BIND METHOD
// Create new function with bound this
const greetJohn = person.greet.bind(person);
greetJohn('Hey', '!!!'); // 'Hey, John!!!'
// setTimeout problem and solution
const user = {
name: 'Alice',
printName: function() {
console.log(this.name);
}
};
// Problem: this is lost
setTimeout(user.printName, 1000); // undefined
// Solution: bind this
setTimeout(user.printName.bind(user), 1000); // 'Alice'
// Event handler
const button = {
label: 'Click me',
handleClick: function() {
console.log(this.label + ' was clicked');
}
};
// document.getElementById('btn')
// .addEventListener('click', button.handleClick.bind(button));
// Partial application with bind
function multiply(a, b) {
return a * b;
}
const double = multiply.bind(null, 2);
const triple = multiply.bind(null, 3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
// COMPARISON EXAMPLE
function introduce(greeting, profession) {
console.log(`${greeting}, I am ${this.name}, a ${profession}`);
}
const john = { name: 'John' };
// call - execute now, individual args
introduce.call(john, 'Hello', 'Developer');
// 'Hello, I am John, a Developer'
// apply - execute now, array of args
introduce.apply(john, ['Hi', 'Engineer']);
// 'Hi, I am John, a Engineer'
// bind - return new function
const johnIntro = introduce.bind(john);
johnIntro('Hey', 'Programmer');
// 'Hey, I am John, a Programmer'
// REAL-WORLD SCENARIOS
// Scenario 1: React class component
class MyComponent {
constructor() {
this.state = { count: 0 };
// Bind methods in constructor
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({ count: this.state.count + 1 });
}
}
// Scenario 2: Array-like to Array
function convertToArray() {
return Array.prototype.slice.call(arguments);
}
// Scenario 3: Finding max in array of arrays
const data = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
const maxValues = data.map(arr => Math.max.apply(null, arr));
console.log(maxValues); // [3, 6, 9]
// Scenario 4: Safe hasOwnProperty
const obj = Object.create(null);
const hasProp = Object.prototype.hasOwnProperty.call(obj, 'key');
// IMPLEMENTATION EXAMPLES
// Simple call implementation
Function.prototype.myCall = function(context, ...args) {
context = context || globalThis;
const fnSymbol = Symbol();
context[fnSymbol] = this;
const result = context[fnSymbol](...args);
delete context[fnSymbol];
return result;
};
// Simple bind implementation
Function.prototype.myBind = function(context, ...boundArgs) {
const fn = this;
return function(...args) {
return fn.apply(context, [...boundArgs, ...args]);
};
};151. Explain currying and partial application. How are they different? Provide practical examples.
Difficulty: HardType: SubjectiveTopic: Functional JS
Currying and partial application are both function transformation techniques, but they work differently.
Currying:
Transforms a multi-argument function into a chain of single-argument functions. Each function takes exactly one argument and returns another function. The transformation is complete, all arguments become separate functions. Enables calling with one argument at a time.
Partial application:
Pre-fills some arguments of a function and returns a new function expecting remaining arguments. Does not require single-argument functions. The new function can take multiple arguments. More flexible than currying.
Key differences:
Currying always returns unary functions, one argument each. Partial application returns functions that can take multiple arguments. Currying is a complete transformation. Partial application is selective fixing. Currying enables easier composition. Partial application is more practical for everyday use.
When to use currying:
Function composition pipelines. When you want uniform interface of single-argument functions. Mathematical or functional programming patterns. Creating reusable function factories.
When to use partial application:
Creating specialized versions of generic functions. Event handlers with pre-set context. Configuration functions. Creating utility functions from general ones.
Practical benefits:
Code reusability by creating specialized functions. Better readability with descriptive function names. Delayed execution with pre-configured parameters. Easier testing of complex functions.
Both techniques are fundamental to functional programming and common in interviews testing advanced JavaScript knowledge.
Example Code
// CURRYING
// Regular function
function add(a, b, c) {
return a + b + c;
}
console.log(add(1, 2, 3)); // 6
// Curried version - each takes one argument
function addCurried(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
console.log(addCurried(1)(2)(3)); // 6
// Arrow function currying
const addArrow = a => b => c => a + b + c;
// Creating specialized functions
const add1 = addArrow(1);
const add1And2 = add1(2);
console.log(add1And2(3)); // 6
console.log(add1And2(5)); // 8
// Generic curry function
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return function(...nextArgs) {
return curried.apply(this, args.concat(nextArgs));
};
};
}
const curriedAdd = curry(add);
// All these work:
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
// PARTIAL APPLICATION
function multiply(a, b, c) {
return a * b * c;
}
// Using bind for partial application
const multiplyBy2 = multiply.bind(null, 2);
console.log(multiplyBy2(3, 4)); // 24 (2 * 3 * 4)
// Custom partial function
function partial(fn, ...fixedArgs) {
return function(...remainingArgs) {
return fn(...fixedArgs, ...remainingArgs);
};
}
const double = partial(multiply, 2, 1);
console.log(double(5)); // 10 (2 * 1 * 5)
// KEY DIFFERENCE DEMONSTRATION
// Currying - MUST call with one arg at a time (or use curry helper)
const curriedMultiply = a => b => c => a * b * c;
// curriedMultiply(2, 3, 4); // Error! Need ()()() syntax
console.log(curriedMultiply(2)(3)(4)); // 24
// Partial - can call with multiple args
const partialMultiply = partial(multiply, 2);
console.log(partialMultiply(3, 4)); // 24 (works!)
// PRACTICAL EXAMPLES
// Example 1: Logging system with currying
const log = level => timestamp => message => {
console.log(`[${level}] ${timestamp}: ${message}`);
};
const errorLog = log('ERROR');
const errorLogToday = errorLog('2024-01-15');
errorLogToday('Database connection failed');
errorLogToday('Timeout error');
// Example 2: API calls with partial application
function apiCall(method, url, headers, body) {
console.log(`${method} ${url}`, { headers, body });
}
const postRequest = partial(apiCall, 'POST');
const postToUsers = partial(postRequest, '/api/users');
postToUsers({ auth: 'token' }, { name: 'John' });
// Example 3: Discount calculator with currying
const discount = percent => price => {
return price - (price * percent / 100);
};
const tenPercentOff = discount(10);
const twentyPercentOff = discount(20);
console.log(tenPercentOff(100)); // 90
console.log(twentyPercentOff(100)); // 80
// Example 4: Validation with partial application
function validate(type, message, value) {
if (type === 'email') {
return value.includes('@') || message;
}
if (type === 'minLength') {
return value.length >= 8 || message;
}
}
const validateEmail = partial(validate, 'email', 'Invalid email');
const validatePassword = partial(validate, 'minLength', 'Too short');
console.log(validateEmail('test@email.com')); // true
console.log(validatePassword('12345')); // 'Too short'
// Example 5: Function composition with currying
const add5 = x => x + 5;
const multiply2 = x => x * 2;
const subtract3 = x => x - 3;
// Compose works better with curried functions
const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);
const calculate = compose(subtract3, multiply2, add5);
console.log(calculate(10)); // ((10 + 5) * 2) - 3 = 27
// WHEN TO USE WHAT
// Use currying for:
// - Function composition
// - Creating chains of specialized functions
// - Functional programming patterns
// Use partial application for:
// - Creating utility functions
// - Event handlers with context
// - Configuration functions
// - Everyday practical coding152. What is an IIFE? Explain the module pattern and how IIFE enables data privacy.
Difficulty: MediumType: SubjectiveTopic: JS Modules
IIFE stands for Immediately Invoked Function Expression. It is a function that executes immediately after being defined.
IIFE syntax:
Wrap function in parentheses to make it an expression. Add parentheses at the end to invoke it. Can use regular or arrow functions. Can pass arguments to the IIFE.
Purpose of IIFE:
Creates a new scope to avoid polluting global scope. Enables data privacy through closures. Provides encapsulation for variables and functions. Executes initialization code once.
Data privacy with IIFE:
Variables inside IIFE are not accessible from outside. Only returned values or objects are exposed. Creates private variables and public API. This is the basis of the module pattern.
Module pattern:
Uses IIFE to create a module with private and public members. Returns an object with public methods. Private variables are accessible only through returned methods. Provides encapsulation and information hiding.
Benefits:
Prevents global namespace pollution. Creates truly private data. Organizes code into reusable modules. Avoids variable collisions. Enables singleton pattern.
Modern alternatives:
ES6 modules with import and export. Block scope with let and const. Classes with private fields using hash.
When to use IIFE today:
Legacy code maintenance. When ES6 modules are not available. Quick scripts that need isolation. Library development for backward compatibility.
IIFE and module pattern were fundamental before ES6 modules and still appear in interviews and legacy code.
Example Code
// BASIC IIFE SYNTAX
// Function declaration (not IIFE)
function test() {
console.log('Not immediately invoked');
}
// IIFE - executes immediately
(function() {
console.log('Immediately invoked!');
})();
// Arrow function IIFE
(() => {
console.log('Arrow IIFE');
})();
// Alternative syntax (both work)
(function() {
console.log('Style 1');
}());
// IIFE with parameters
(function(name, age) {
console.log(name + ' is ' + age);
})('John', 30);
// IIFE returning a value
const result = (function() {
return 2 + 2;
})();
console.log(result); // 4
// DATA PRIVACY WITH IIFE
const counter = (function() {
// Private variable
let count = 0;
// Private function
function validateIncrement(value) {
return value > 0;
}
// Public API
return {
increment: function(value = 1) {
if (validateIncrement(value)) {
count += value;
}
return count;
},
decrement: function() {
count--;
return count;
},
getCount: function() {
return count;
},
reset: function() {
count = 0;
}
};
})();
console.log(counter.increment()); // 1
console.log(counter.increment(5)); // 6
console.log(counter.getCount()); // 6
// console.log(counter.count); // undefined (private!)
// counter.validateIncrement(); // Error (private!)
// MODULE PATTERN
const calculator = (function() {
// Private state
let history = [];
// Private helper
function addToHistory(operation) {
history.push(operation);
}
// Public API
return {
add: function(a, b) {
const result = a + b;
addToHistory(`${a} + ${b} = ${result}`);
return result;
},
subtract: function(a, b) {
const result = a - b;
addToHistory(`${a} - ${b} = ${result}`);
return result;
},
getHistory: function() {
return [...history]; // Return copy
},
clearHistory: function() {
history = [];
}
};
})();
console.log(calculator.add(5, 3)); // 8
console.log(calculator.subtract(10, 4)); // 6
console.log(calculator.getHistory());
// ['5 + 3 = 8', '10 - 4 = 6']
// REVEALING MODULE PATTERN
const userModule = (function() {
// Private
let users = [];
function validateUser(user) {
return user.name && user.email;
}
function addUser(user) {
if (validateUser(user)) {
users.push(user);
return true;
}
return false;
}
function getUsers() {
return users;
}
function removeUser(email) {
users = users.filter(u => u.email !== email);
}
// Reveal public methods
return {
add: addUser,
getAll: getUsers,
remove: removeUser
};
})();
userModule.add({ name: 'John', email: 'john@example.com' });
console.log(userModule.getAll());
// SINGLETON PATTERN WITH IIFE
const database = (function() {
let instance;
function createInstance() {
return {
connect: function() {
console.log('Connected to database');
},
query: function(sql) {
console.log('Executing:', sql);
}
};
}
return {
getInstance: function() {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
const db1 = database.getInstance();
const db2 = database.getInstance();
console.log(db1 === db2); // true (same instance)
// AVOIDING GLOBAL POLLUTION
// Bad - pollutes global scope
var temp = 'global';
var helper = function() {};
// Good - isolated in IIFE
(function() {
var temp = 'isolated';
var helper = function() {};
// Do work
})();
// console.log(temp); // 'global' (not affected)
// console.log(helper); // Still accessible
// MODERN ALTERNATIVES
// ES6 Modules
// export const myModule = { ... };
// import { myModule } from './module.js';
// Block scope
{
const privateVar = 'isolated';
// Use privateVar
}
// console.log(privateVar); // ReferenceError
// Class with private fields (ES2022)
class MyClass {
#privateField = 'private';
getPrivate() {
return this.#privateField;
}
}153. What are pure functions? Explain their benefits and provide examples of pure and impure functions.
Difficulty: MediumType: SubjectiveTopic: Functional JS
Pure functions are functions that satisfy two conditions: they return the same output for the same input and have no side effects.
First condition: Deterministic behavior:
Given the same arguments, pure function always returns the same result. The output depends only on input parameters. No dependency on external state or global variables. Predictable and reliable behavior.
Second condition: No side effects:
Does not modify external state or global variables. Does not mutate function arguments. Does not perform I/O operations like console.log, file read write, or network requests. Does not modify DOM. Does not generate random numbers or use current time.
Benefits of pure functions:
Easier to test because behavior is predictable. Easier to debug with no hidden dependencies. Can be safely parallelized or cached with memoization. Enable referential transparency, can replace function call with its result. More maintainable and understandable code. Support functional programming patterns.
Common impure operations:
Modifying global variables. Mutating function parameters. Console logging or any I/O. Using Math.random or Date. Modifying DOM. Making API calls. Reading from or writing to files.
Making impure functions pure:
Return new values instead of modifying existing ones. Accept all dependencies as parameters. Use pure functions for calculations, separate I/O. Return new objects and arrays instead of mutating.
When impure functions are necessary:
User interaction and event handling. API calls and data fetching. Logging and debugging. File system operations. Any I/O operations.
Best practices:
Maximize pure functions in your codebase. Isolate impure code at the edges of your application. Keep business logic pure. Use pure functions for data transformations.
Understanding pure functions is fundamental to functional programming and writing maintainable code.
Example Code
// PURE FUNCTIONS
// Pure - same input, same output, no side effects
function add(a, b) {
return a + b;
}
console.log(add(2, 3)); // Always 5
console.log(add(2, 3)); // Always 5
// Pure - doesn't modify input
function doubleArray(arr) {
return arr.map(n => n * 2);
}
const numbers = [1, 2, 3];
const doubled = doubleArray(numbers);
console.log(numbers); // [1, 2, 3] (unchanged)
console.log(doubled); // [2, 4, 6]
// Pure - string manipulation
function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
}
console.log(capitalize('hello')); // 'Hello'
console.log(capitalize('hello')); // 'Hello' (same)
// Pure - object transformation
function updateUser(user, updates) {
return { ...user, ...updates }; // New object
}
const user = { name: 'John', age: 30 };
const updated = updateUser(user, { age: 31 });
console.log(user.age); // 30 (unchanged)
console.log(updated.age); // 31
// IMPURE FUNCTIONS
// Impure - modifies external variable
let total = 0;
function addToTotal(value) {
total += value; // Side effect!
return total;
}
console.log(addToTotal(5)); // 5
console.log(addToTotal(5)); // 10 (different!)
// Impure - mutates argument
function addItemImpure(arr, item) {
arr.push(item); // Mutates input!
return arr;
}
// Impure - I/O operation
function logValue(value) {
console.log(value); // Side effect!
return value;
}
// Impure - uses external state
let multiplier = 2;
function multiplyByMultiplier(n) {
return n * multiplier; // Depends on external variable
}
console.log(multiplyByMultiplier(5)); // 10
multiplier = 3;
console.log(multiplyByMultiplier(5)); // 15 (different!)
// Impure - random output
function getRandomNumber() {
return Math.random(); // Non-deterministic
}
// Impure - current time
function getCurrentTimestamp() {
return Date.now(); // Different each call
}
// CONVERTING IMPURE TO PURE
// Impure version
function discountPriceImpure(price) {
const discount = 0.1; // Hard-coded
console.log('Calculating discount'); // Side effect
return price - (price * discount);
}
// Pure version
function discountPricePure(price, discount) {
return price - (price * discount);
}
// Separate side effects
function calculateAndLog(price, discount) {
const result = discountPricePure(price, discount); // Pure
console.log('Result:', result); // Impure, but isolated
return result;
}
// REAL-WORLD EXAMPLES
// Pure - shopping cart total
function calculateTotal(items) {
return items.reduce((sum, item) => {
return sum + (item.price * item.quantity);
}, 0);
}
const cart = [
{ name: 'Book', price: 10, quantity: 2 },
{ name: 'Pen', price: 2, quantity: 5 }
];
console.log(calculateTotal(cart)); // 30
// Pure - filtering and mapping
function getActiveUserNames(users) {
return users
.filter(user => user.active)
.map(user => user.name);
}
// Pure - form validation
function validateEmail(email) {
return email.includes('@') && email.includes('.');
}
function validatePassword(password) {
return password.length >= 8;
}
function validateForm(data) {
return {
emailValid: validateEmail(data.email),
passwordValid: validatePassword(data.password)
};
}
// BENEFITS DEMONSTRATION
// 1. Easy testing
function multiply(a, b) {
return a * b;
}
// Simple test
console.assert(multiply(2, 3) === 6);
console.assert(multiply(2, 3) === 6); // Always same!
// 2. Memoization (caching)
function memoize(fn) {
const cache = {};
return function(...args) {
const key = JSON.stringify(args);
if (key in cache) {
console.log('From cache');
return cache[key];
}
const result = fn(...args);
cache[key] = result;
return result;
};
}
// Only works reliably with pure functions
const memoizedMultiply = memoize(multiply);
console.log(memoizedMultiply(5, 6)); // Computes
console.log(memoizedMultiply(5, 6)); // From cache
// 3. Parallelization (conceptual)
const data = [1, 2, 3, 4, 5, 6, 7, 8];
// Safe to parallelize because pure
const doubled = data.map(n => n * 2);
// BEST PRACTICES
// Do: Keep business logic pure
function calculateDiscount(price, percentage) {
return price * (percentage / 100);
}
// Do: Separate I/O from logic
function processOrder(order) {
const total = calculateTotal(order.items); // Pure
saveToDatabase(total); // Impure, but separated
return total;
}
// Don't: Mix concerns
function badCalculate(price) {
console.log('Calculating...'); // Impure!
const result = price * 0.9;
saveToDatabase(result); // Impure!
return result;
}154. What is the difference between getElementById and querySelector?
Difficulty: EasyType: MCQTopic: DOM Basics
- They are exactly the same
- getElementById is faster and returns single element, querySelector is more flexible
- querySelector only works with classes
- getElementById can select multiple elements
GetElementById selects a single element by its ID attribute. It is faster because it directly uses the browser's optimized ID lookup. It returns the element or null if not found.
QuerySelector uses CSS selector syntax and is more flexible. You can select by ID, class, attribute, or any CSS selector. It returns the first matching element or null.
QuerySelector is slower but more powerful. Use getElementById when selecting by ID for better performance. Use querySelector when you need CSS selector flexibility.
Both return a single element. For multiple elements, use getElementsByClassName or querySelectorAll.
Understanding DOM selection methods is fundamental to JavaScript web development.
Correct Answer: getElementById is faster and returns single element, querySelector is more flexible
Example Code
// getElementById - fast, ID only
const header = document.getElementById('header');
console.log(header); // <div id="header">...</div>
// Must use ID without #
// const wrong = document.getElementById('#header'); // Wrong!
// querySelector - flexible, any CSS selector
const header2 = document.querySelector('#header');
const firstButton = document.querySelector('button');
const activeItem = document.querySelector('.item.active');
const input = document.querySelector('input[type="email"]');
// querySelector returns first match only
const buttons = document.querySelectorAll('button'); // All buttons
console.log(buttons.length); // e.g., 5
const firstBtn = document.querySelector('button'); // First button only
// Performance comparison
console.time('getElementById');
for (let i = 0; i < 10000; i++) {
document.getElementById('header');
}
console.timeEnd('getElementById'); // Faster
console.time('querySelector');
for (let i = 0; i < 10000; i++) {
document.querySelector('#header');
}
console.timeEnd('querySelector'); // Slower
// Complex selectors with querySelector
const nested = document.querySelector('div.container > p.text');
const nthChild = document.querySelector('li:nth-child(3)');
const notClass = document.querySelector('div:not(.excluded)');
// Other selection methods
const byClass = document.getElementsByClassName('item'); // HTMLCollection
const byTag = document.getElementsByTagName('p'); // HTMLCollection
const allDivs = document.querySelectorAll('div'); // NodeList
// Difference: HTMLCollection vs NodeList
// HTMLCollection is live, NodeList from querySelectorAll is static
const liveList = document.getElementsByClassName('item');
const staticList = document.querySelectorAll('.item');
console.log(liveList.length); // e.g., 3
console.log(staticList.length); // e.g., 3
// Add new element with class 'item'
document.body.innerHTML += '<div class="item">New</div>';
console.log(liveList.length); // 4 (updated automatically!)
console.log(staticList.length); // 3 (stays same)155. What is the difference between innerHTML, textContent, and innerText?
Difficulty: EasyType: MCQTopic: DOM Basics
- They are the same
- innerHTML parses HTML, textContent gets all text, innerText gets visible text
- innerHTML is faster than textContent
- innerText works with XML, innerHTML doesn't
InnerHTML gets or sets the HTML content including all tags. It parses HTML, so it is slower but more powerful. Use it when you need to insert HTML elements.
TextContent gets or sets all text content without HTML parsing. It is faster and safer than innerHTML. It includes text from hidden elements and script tags. Use it for plain text to avoid XSS attacks.
InnerText gets only visible text, respecting CSS styling. It triggers reflow, so it is the slowest. Hidden elements are excluded. It considers line breaks and formatting.
For security, always use textContent for user input to prevent XSS. Use innerHTML only for trusted content. InnerText is useful when you need visible text only.
Understanding these differences prevents security issues and performance problems.
Correct Answer: innerHTML parses HTML, textContent gets all text, innerText gets visible text
Example Code
const div = document.getElementById('demo');
// Assume: <div id="demo">Hello <span style="display:none">Hidden</span> <b>World</b></div>
// innerHTML - includes HTML tags
console.log(div.innerHTML);
// 'Hello <span style="display:none">Hidden</span> <b>World</b>'
// textContent - all text, no HTML
console.log(div.textContent);
// 'Hello Hidden World' (includes hidden text)
// innerText - visible text only
console.log(div.innerText);
// 'Hello World' (excludes hidden text)
// SETTING CONTENT
// innerHTML - can add HTML elements
div.innerHTML = '<p>New <strong>content</strong></p>';
// Creates actual <p> and <strong> elements
// textContent - adds as plain text
div.textContent = '<p>New content</p>';
// Displays literally: <p>New content</p>
// innerText - adds as plain text with formatting
div.innerText = 'Line 1\nLine 2';
// Respects line breaks
// SECURITY - XSS Attack Prevention
const userInput = '<img src=x onerror="alert(\'XSS\')"> ';
// UNSAFE - executes script!
// div.innerHTML = userInput; // Alert pops up!
// SAFE - displays as text
div.textContent = userInput;
// Shows: <img src=x onerror="alert('XSS')">
// PERFORMANCE
console.time('innerHTML');
for (let i = 0; i < 1000; i++) {
div.innerHTML = 'Test';
}
console.timeEnd('innerHTML'); // Slowest (parses HTML)
console.time('textContent');
for (let i = 0; i < 1000; i++) {
div.textContent = 'Test';
}
console.timeEnd('textContent'); // Fastest
console.time('innerText');
for (let i = 0; i < 1000; i++) {
div.innerText = 'Test';
}
console.timeEnd('innerText'); // Slow (triggers reflow)
// PRACTICAL EXAMPLES
// Get form input safely
const username = document.getElementById('username').value;
document.getElementById('greeting').textContent = `Hello, ${username}`;
// Safe from XSS
// Insert HTML from template
const template = '<div class="card"><h3>Title</h3></div>';
document.getElementById('container').innerHTML = template;
// Use only for trusted content
// Get visible text for copying
const article = document.getElementById('article');
const visibleText = article.innerText;
// Excludes hidden content, good for copy function156. What is the difference between addEventListener and onclick?
Difficulty: MediumType: MCQTopic: DOM Events
- No difference
- addEventListener can add multiple handlers, onclick can only have one
- onclick is faster
- addEventListener only works with buttons
AddEventListener is the modern way to attach event handlers. You can add multiple handlers for the same event. It provides options for capture, once, and passive. It separates JavaScript from HTML.
Onclick is the older inline approach. Only one handler can exist at a time. Setting onclick again overwrites the previous handler. It is simpler but less flexible.
AddEventListener is preferred because it is more flexible, allows multiple handlers, provides better control, and follows best practices of separation of concerns.
You can remove listeners added with addEventListener using removeEventListener. You cannot remove onclick handlers the same way.
Understanding event handling is crucial for interactive web applications.
Correct Answer: addEventListener can add multiple handlers, onclick can only have one
Example Code
const button = document.getElementById('myButton');
// onclick - only one handler
button.onclick = function() {
console.log('Click 1');
};
button.onclick = function() {
console.log('Click 2');
};
// Only 'Click 2' executes (overwrites first)
// addEventListener - multiple handlers
button.addEventListener('click', function() {
console.log('Handler 1');
});
button.addEventListener('click', function() {
console.log('Handler 2');
});
// Both execute: 'Handler 1' then 'Handler 2'
// Removing event listeners
function handleClick() {
console.log('Clicked');
}
button.addEventListener('click', handleClick);
// Can remove later
button.removeEventListener('click', handleClick);
// Cannot remove anonymous functions
button.addEventListener('click', function() {
console.log('Cannot remove this');
});
// Event listener options
// Capture phase
button.addEventListener('click', function() {
console.log('Capture phase');
}, true);
// Once - executes only once, then removes itself
button.addEventListener('click', function() {
console.log('Fires once');
}, { once: true });
// Passive - improves scroll performance
button.addEventListener('touchstart', function() {
// Cannot call preventDefault
}, { passive: true });
// Multiple options
button.addEventListener('click', function() {
console.log('Options');
}, {
capture: false,
once: false,
passive: false
});
// INLINE HTML (avoid this)
// <button onclick="handleClick()">Click</button>
// Mixes HTML and JavaScript, bad practice
// PRACTICAL COMPARISON
// onclick approach
const btn1 = document.getElementById('btn1');
btn1.onclick = () => console.log('One');
btn1.onclick = () => console.log('Two'); // Overwrites
// addEventListener approach
const btn2 = document.getElementById('btn2');
btn2.addEventListener('click', () => console.log('One'));
btn2.addEventListener('click', () => console.log('Two')); // Both work
// Event object
button.addEventListener('click', function(event) {
console.log('Target:', event.target);
console.log('Type:', event.type);
console.log('Timestamp:', event.timeStamp);
event.preventDefault(); // Prevent default action
event.stopPropagation(); // Stop bubbling
});157. What is event bubbling in JavaScript?
Difficulty: HardType: MCQTopic: DOM Events
- Events float to the top of the page
- Events propagate from target element up to parent elements
- Events happen in random order
- Events are delayed
Event bubbling is when an event triggered on a child element propagates up through its parent elements to the root.
The event starts at the target element where it occurred. It then bubbles up through each ancestor element. Each ancestor's event handlers are triggered in order.
Event capturing is the opposite, going from root down to target. Most events bubble, but some like focus and blur do not.
You can stop bubbling with event.stopPropagation. This prevents the event from reaching parent elements.
Event delegation uses bubbling to handle events on multiple children with one parent listener.
Understanding bubbling is essential for event delegation and preventing unintended behavior.
Correct Answer: Events propagate from target element up to parent elements
Example Code
// HTML structure:
// <div id="outer">
// <div id="middle">
// <button id="inner">Click</button>
// </div>
// </div>
const outer = document.getElementById('outer');
const middle = document.getElementById('middle');
const inner = document.getElementById('inner');
// Add listeners to all three
outer.addEventListener('click', () => {
console.log('Outer clicked');
});
middle.addEventListener('click', () => {
console.log('Middle clicked');
});
inner.addEventListener('click', () => {
console.log('Inner clicked');
});
// When button clicked, output:
// 'Inner clicked'
// 'Middle clicked'
// 'Outer clicked'
// Event bubbles from inner to outer!
// STOPPING PROPAGATION
inner.addEventListener('click', (event) => {
console.log('Inner clicked');
event.stopPropagation(); // Stop bubbling!
});
// Now only 'Inner clicked' appears
// EVENT PHASES
// 1. Capture phase (root to target)
outer.addEventListener('click', () => {
console.log('Outer capture');
}, true); // true = capture phase
// 2. Target phase
inner.addEventListener('click', () => {
console.log('Target');
});
// 3. Bubble phase (target to root)
outer.addEventListener('click', () => {
console.log('Outer bubble');
}, false); // false = bubble phase (default)
// Output when inner clicked:
// 'Outer capture' (capture phase)
// 'Target' (target phase)
// 'Outer bubble' (bubble phase)
// EVENT DELEGATION
// Instead of adding listener to each item
const list = document.getElementById('list');
// Add one listener to parent
list.addEventListener('click', (event) => {
if (event.target.tagName === 'LI') {
console.log('Clicked:', event.target.textContent);
}
});
// Works for dynamically added items too!
const newItem = document.createElement('li');
newItem.textContent = 'New Item';
list.appendChild(newItem);
// Clicking new item works without adding listener!
// PRACTICAL EXAMPLE - Modal
const modal = document.getElementById('modal');
const modalContent = document.querySelector('.modal-content');
const closeBtn = document.querySelector('.close');
// Close modal when clicking outside
modal.addEventListener('click', () => {
modal.style.display = 'none';
});
// Don't close when clicking content
modalContent.addEventListener('click', (event) => {
event.stopPropagation(); // Don't bubble to modal
});
// Close button
closeBtn.addEventListener('click', () => {
modal.style.display = 'none';
});
// PREVENTING DEFAULT AND BUBBLING
const link = document.querySelector('a');
link.addEventListener('click', (event) => {
event.preventDefault(); // Don't follow link
event.stopPropagation(); // Don't bubble
console.log('Link clicked but not followed');
});
// event.stopImmediatePropagation()
// Stops other handlers on same element too
inner.addEventListener('click', (event) => {
console.log('First handler');
event.stopImmediatePropagation();
});
inner.addEventListener('click', () => {
console.log('Second handler'); // Won't run
});158. What is event.target vs event.currentTarget?
Difficulty: MediumType: MCQTopic: DOM Events
- They are the same
- target is element that triggered event, currentTarget is element with listener
- target is parent, currentTarget is child
- currentTarget is always the body
Event.target is the actual element that triggered the event. It is the innermost element that was clicked or interacted with.
Event.currentTarget is the element that has the event listener attached. During bubbling, this is the current element processing the event.
Target can be a child element, but currentTarget is always the element with the listener. This distinction is important for event delegation.
Target can change during bubbling as different elements are processed. CurrentTarget always refers to the element with the listener.
Use target to find what was actually clicked. Use currentTarget to reference the element with the handler.
Understanding this difference is crucial for event delegation patterns.
Correct Answer: target is element that triggered event, currentTarget is element with listener
Example Code
// HTML:
// <div id="parent">
// <button id="child">Click Me</button>
// </div>
const parent = document.getElementById('parent');
parent.addEventListener('click', function(event) {
console.log('target:', event.target.id);
console.log('currentTarget:', event.currentTarget.id);
});
// When clicking button:
// target: 'child' (button was clicked)
// currentTarget: 'parent' (listener is on parent)
// When clicking parent directly:
// target: 'parent'
// currentTarget: 'parent'
// (both same when clicking listener element)
// EVENT DELEGATION EXAMPLE
const list = document.getElementById('list');
// <ul id="list">
// <li>Item 1</li>
// <li>Item 2</li>
// </ul>
list.addEventListener('click', function(event) {
console.log('Clicked:', event.target.textContent);
console.log('Listener on:', event.currentTarget.id);
// Check if clicked element is LI
if (event.target.tagName === 'LI') {
event.target.style.backgroundColor = 'yellow';
}
});
// OTHER EVENT PROPERTIES
document.addEventListener('click', function(event) {
console.log('Type:', event.type); // 'click'
console.log('Target:', event.target);
console.log('CurrentTarget:', event.currentTarget); // document
console.log('Timestamp:', event.timeStamp);
console.log('Bubbles:', event.bubbles); // true for most events
console.log('Cancelable:', event.cancelable);
});
// MOUSE EVENT PROPERTIES
document.addEventListener('click', function(event) {
console.log('ClientX:', event.clientX); // X relative to viewport
console.log('ClientY:', event.clientY); // Y relative to viewport
console.log('PageX:', event.pageX); // X relative to document
console.log('PageY:', event.pageY); // Y relative to document
console.log('ScreenX:', event.screenX); // X relative to screen
console.log('ScreenY:', event.screenY); // Y relative to screen
console.log('Button:', event.button); // 0=left, 1=middle, 2=right
});
// KEYBOARD EVENT PROPERTIES
document.addEventListener('keydown', function(event) {
console.log('Key:', event.key); // 'a', 'Enter', etc.
console.log('Code:', event.code); // 'KeyA', 'Enter', etc.
console.log('KeyCode:', event.keyCode); // Deprecated
console.log('AltKey:', event.altKey); // true if Alt pressed
console.log('CtrlKey:', event.ctrlKey); // true if Ctrl pressed
console.log('ShiftKey:', event.shiftKey);
});
// PRACTICAL EXAMPLE - Click outside to close
const dropdown = document.getElementById('dropdown');
const button = document.getElementById('dropdownBtn');
button.addEventListener('click', (event) => {
event.stopPropagation();
dropdown.classList.toggle('open');
});
document.addEventListener('click', (event) => {
// If click is outside dropdown
if (!dropdown.contains(event.target)) {
dropdown.classList.remove('open');
}
});
// FORM EVENTS
const form = document.getElementById('myForm');
form.addEventListener('submit', function(event) {
event.preventDefault(); // Stop form submission
console.log('Form submitted');
console.log('Target:', event.target); // The <form> element
// Get form data
const formData = new FormData(event.target);
console.log(formData.get('username'));
});159. What is the correct way to create and add an element to the DOM?
Difficulty: EasyType: MCQTopic: DOM Basics
- createElement, then appendChild or append
- Just use innerHTML
- Use document.write()
- Elements are created automatically
The proper way is to create an element with createElement, configure it, then add it to the DOM with appendChild or append.
CreateElement creates a new element node. You then set its properties, attributes, and content. Finally, append it to a parent element to make it visible.
AppendChild is the traditional method that adds one node. Append is newer and can add multiple nodes and text at once.
This approach is safer and more efficient than innerHTML for adding elements. It avoids parsing HTML and potential XSS issues.
You can also use insertBefore, insertAdjacentElement, or replaceChild for different insertion needs.
Understanding element creation is fundamental to dynamic web pages.
Correct Answer: createElement, then appendChild or append
Example Code
// CREATE ELEMENT
const div = document.createElement('div');
console.log(div); // <div></div> (not in DOM yet)
// Set properties
div.id = 'myDiv';
div.className = 'container';
div.textContent = 'Hello World';
// Set attributes
div.setAttribute('data-id', '123');
div.setAttribute('title', 'Tooltip');
// Add styles
div.style.color = 'blue';
div.style.padding = '10px';
// ADD TO DOM with appendChild
document.body.appendChild(div);
// Now visible in page
// CREATE COMPLEX ELEMENT
const card = document.createElement('div');
card.className = 'card';
const title = document.createElement('h3');
title.textContent = 'Card Title';
const text = document.createElement('p');
text.textContent = 'Card description';
const button = document.createElement('button');
button.textContent = 'Click Me';
button.addEventListener('click', () => {
console.log('Button clicked');
});
// Assemble
card.appendChild(title);
card.appendChild(text);
card.appendChild(button);
// Add to page
document.body.appendChild(card);
// APPEND VS APPENDCHILD
const container = document.getElementById('container');
// appendChild - one node only, returns appended node
const p1 = document.createElement('p');
p1.textContent = 'Paragraph 1';
container.appendChild(p1);
// append - multiple nodes and text, returns undefined
const p2 = document.createElement('p');
p2.textContent = 'Paragraph 2';
container.append(p2, 'Some text', document.createElement('span'));
// PREPEND - add at beginning
const first = document.createElement('div');
first.textContent = 'First';
container.prepend(first); // Adds at start
// INSERT AT SPECIFIC POSITION
const reference = document.getElementById('reference');
const newElement = document.createElement('div');
// Insert before reference
reference.parentNode.insertBefore(newElement, reference);
// insertAdjacentElement
reference.insertAdjacentElement('beforebegin', newElement); // Before element
reference.insertAdjacentElement('afterbegin', newElement); // First child
reference.insertAdjacentElement('beforeend', newElement); // Last child
reference.insertAdjacentElement('afterend', newElement); // After element
// REMOVING ELEMENTS
// Modern way
element.remove();
// Old way
parent.removeChild(element);
// REPLACING ELEMENTS
const oldElement = document.getElementById('old');
const newElement2 = document.createElement('div');
newElement2.textContent = 'New';
oldElement.replaceWith(newElement2); // Modern
oldElement.parentNode.replaceChild(newElement2, oldElement); // Old
// CLONING ELEMENTS
const original = document.getElementById('template');
// Shallow clone (no children)
const shallow = original.cloneNode(false);
// Deep clone (with children)
const deep = original.cloneNode(true);
// DOCUMENT FRAGMENT (efficient for multiple elements)
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const item = document.createElement('li');
item.textContent = `Item ${i}`;
fragment.appendChild(item);
}
// Add all at once (one reflow instead of 100)
document.getElementById('list').appendChild(fragment);
// PRACTICAL EXAMPLE - Create card
function createCard(title, description) {
const card = document.createElement('div');
card.className = 'card';
const heading = document.createElement('h3');
heading.textContent = title;
const para = document.createElement('p');
para.textContent = description;
card.append(heading, para);
return card;
}
const myCard = createCard('Title', 'Description');
document.body.appendChild(myCard);160. What is the classList property used for?
Difficulty: EasyType: MCQTopic: DOM Basics
- Creating class lists
- Managing CSS classes on elements with add, remove, toggle methods
- Listing all classes in document
- Styling elements
The classList property provides methods to add, remove, toggle, and check CSS classes on elements.
Add method adds one or more classes. Remove method removes classes. Toggle adds class if absent, removes if present. Contains checks if class exists.
ClassList is better than className because it does not replace all classes. It works with multiple classes easily. It provides convenient methods.
ClassName is a string of all classes, harder to manipulate. ClassList is a DOMTokenList with helpful methods.
Using classList is the modern, recommended way to manage CSS classes.
Understanding classList is essential for dynamic styling in JavaScript.
Correct Answer: Managing CSS classes on elements with add, remove, toggle methods
Example Code
const element = document.getElementById('myElement');
// ADD classes
element.classList.add('active');
element.classList.add('highlight', 'special'); // Multiple at once
// REMOVE classes
element.classList.remove('active');
element.classList.remove('highlight', 'special');
// TOGGLE - add if absent, remove if present
element.classList.toggle('active'); // Adds
element.classList.toggle('active'); // Removes
// Toggle with condition
const isActive = true;
element.classList.toggle('active', isActive); // Adds if isActive is true
// CHECK if class exists
if (element.classList.contains('active')) {
console.log('Element is active');
}
// REPLACE class
element.classList.replace('old-class', 'new-class');
// GET all classes as array
const classes = [...element.classList];
console.log(classes); // ['class1', 'class2']
// ITERATE over classes
element.classList.forEach(className => {
console.log(className);
});
// LENGTH
console.log(element.classList.length); // Number of classes
// ITEM - get class by index
console.log(element.classList.item(0)); // First class
// COMPARE WITH className
// Using className (old way, harder)
element.className = 'class1 class2'; // Replaces all classes
if (element.className.includes('active')) {
// Not reliable if 'active-button' exists
}
element.className += ' new-class'; // Add (awkward)
element.className = element.className.replace('old', 'new'); // Replace
// Using classList (modern, easier)
element.classList.add('new-class');
element.classList.replace('old', 'new');
// PRACTICAL EXAMPLES
// Toggle menu
const menuBtn = document.getElementById('menuBtn');
const menu = document.getElementById('menu');
menuBtn.addEventListener('click', () => {
menu.classList.toggle('open');
});
// Active tab
const tabs = document.querySelectorAll('.tab');
tabs.forEach(tab => {
tab.addEventListener('click', () => {
// Remove active from all
tabs.forEach(t => t.classList.remove('active'));
// Add to clicked
tab.classList.add('active');
});
});
// Dark mode toggle
const toggleBtn = document.getElementById('darkModeToggle');
toggleBtn.addEventListener('click', () => {
document.body.classList.toggle('dark-mode');
// Update button text
const isDark = document.body.classList.contains('dark-mode');
toggleBtn.textContent = isDark ? 'Light Mode' : 'Dark Mode';
});
// Loading state
const button = document.getElementById('submitBtn');
button.addEventListener('click', async () => {
button.classList.add('loading');
button.disabled = true;
await fetchData(); // Async operation
button.classList.remove('loading');
button.disabled = false;
});
// Highlight on scroll
window.addEventListener('scroll', () => {
const sections = document.querySelectorAll('section');
sections.forEach(section => {
const top = section.offsetTop;
const height = section.offsetHeight;
if (window.scrollY >= top - 100 && window.scrollY < top + height) {
section.classList.add('active');
} else {
section.classList.remove('active');
}
});
});161. What is event delegation and why is it useful?
Difficulty: MediumType: MCQTopic: DOM Events
- Delegating events to other developers
- Using a parent element to handle events for its children
- Delaying event execution
- Copying events between elements
Event delegation is attaching a single event listener to a parent element to handle events from its children using event bubbling.
Instead of adding listeners to many child elements, you add one listener to the parent. When a child is clicked, the event bubbles up to the parent.
Benefits include better performance with fewer event listeners, automatic handling of dynamically added elements, and less memory usage.
You check event.target to determine which child triggered the event. Then handle it accordingly based on the target.
Event delegation is a fundamental pattern in JavaScript, especially for lists and dynamic content.
Understanding delegation shows advanced knowledge of events and DOM manipulation.
Correct Answer: Using a parent element to handle events for its children
Example Code
// WITHOUT EVENT DELEGATION (bad for many elements)
const items = document.querySelectorAll('.item');
items.forEach(item => {
item.addEventListener('click', () => {
console.log('Item clicked');
});
});
// Problems:
// - 100 items = 100 event listeners (memory!)
// - Doesn't work for dynamically added items
// WITH EVENT DELEGATION (efficient)
const list = document.getElementById('list');
list.addEventListener('click', (event) => {
// Check if clicked element is an item
if (event.target.classList.contains('item')) {
console.log('Item clicked:', event.target.textContent);
}
});
// Benefits:
// - One listener for all items
// - Works for items added later
// - Better performance
// PRACTICAL EXAMPLE - Todo List
const todoList = document.getElementById('todoList');
todoList.addEventListener('click', (event) => {
const target = event.target;
// Handle delete button
if (target.classList.contains('delete-btn')) {
target.parentElement.remove();
}
// Handle checkbox
if (target.type === 'checkbox') {
target.parentElement.classList.toggle('completed');
}
// Handle edit button
if (target.classList.contains('edit-btn')) {
const li = target.parentElement;
const text = li.querySelector('.text');
text.contentEditable = true;
text.focus();
}
});
// Add new items dynamically
function addTodo(text) {
const li = document.createElement('li');
li.innerHTML = `
<input type="checkbox">
<span class="text">${text}</span>
<button class="edit-btn">Edit</button>
<button class="delete-btn">Delete</button>
`;
todoList.appendChild(li);
// No need to add event listeners!
}
// ADVANCED DELEGATION - Multiple event types
const container = document.getElementById('container');
// Handle multiple events on parent
container.addEventListener('click', handleClick);
container.addEventListener('dblclick', handleDoubleClick);
container.addEventListener('contextmenu', handleRightClick);
function handleClick(event) {
if (event.target.matches('.button')) {
console.log('Button clicked');
}
}
function handleDoubleClick(event) {
if (event.target.matches('.item')) {
console.log('Item double clicked');
}
}
function handleRightClick(event) {
if (event.target.matches('.card')) {
event.preventDefault();
showContextMenu(event);
}
}
// CHECKING TARGET ELEMENT
// Method 1: classList.contains()
if (event.target.classList.contains('item')) { }
// Method 2: matches() - CSS selector
if (event.target.matches('.item')) { }
if (event.target.matches('button.primary')) { }
// Method 3: tagName
if (event.target.tagName === 'BUTTON') { }
// Method 4: closest() - find ancestor
const card = event.target.closest('.card');
if (card) {
// Clicked somewhere inside a card
}
// DELEGATION WITH CLOSEST
table.addEventListener('click', (event) => {
// Find the row, even if clicked on cell
const row = event.target.closest('tr');
if (row && row.dataset.id) {
console.log('Row clicked:', row.dataset.id);
}
});
// REAL-WORLD EXAMPLE - Image Gallery
const gallery = document.getElementById('gallery');
const modal = document.getElementById('modal');
const modalImg = document.getElementById('modalImg');
gallery.addEventListener('click', (event) => {
if (event.target.tagName === 'IMG') {
modal.style.display = 'block';
modalImg.src = event.target.src;
}
});
modal.addEventListener('click', () => {
modal.style.display = 'none';
});
// WHEN NOT TO USE DELEGATION
// 1. Events that don't bubble (focus, blur)
// 2. Need to bind this to specific element
// 3. Very specific single-element handler
// For non-bubbling events
const input = document.getElementById('myInput');
input.addEventListener('focus', () => {
// Must attach directly
});162. What is the correct way to prevent form submission?
Difficulty: MediumType: MCQTopic: DOM Forms
- return false
- event.preventDefault()
- event.stopSubmit()
- form.cancel()
Event.preventDefault stops the default action of an event. For forms, it prevents page reload on submission.
Call preventDefault in the submit event handler. This allows you to validate data and submit via AJAX instead.
Return false works in inline HTML but is not recommended. It also stops propagation, which may not be intended.
Preventing default is essential for modern web applications that handle forms with JavaScript.
You can also prevent default on links, context menus, and other events.
Understanding preventDefault is crucial for controlling browser behavior.
Correct Answer: event.preventDefault()
Example Code
const form = document.getElementById('myForm');
// PREVENT FORM SUBMISSION
form.addEventListener('submit', (event) => {
event.preventDefault(); // Stop page reload
console.log('Form submitted');
// Get form data
const formData = new FormData(form);
const data = Object.fromEntries(formData);
console.log(data);
// Validate and send via AJAX
if (validateForm(data)) {
submitForm(data);
}
});
// FORM VALIDATION
function validateForm(data) {
const errors = [];
if (!data.username || data.username.trim().length < 3) {
errors.push('Username must be at least 3 characters');
}
if (!data.email || !data.email.includes('@')) {
errors.push('Invalid email');
}
if (!data.password || data.password.length < 8) {
errors.push('Password must be at least 8 characters');
}
if (errors.length > 0) {
displayErrors(errors);
return false;
}
return true;
}
// ACCESSING FORM DATA
// Method 1: FormData API
const formData = new FormData(form);
console.log(formData.get('username'));
console.log(formData.get('email'));
// Convert to object
const dataObj = Object.fromEntries(formData);
console.log(dataObj); // {username: '...', email: '...'}
// Method 2: Direct access
const username = form.elements.username.value;
const email = form.elements.email.value;
// Method 3: querySelector
const username2 = form.querySelector('input[name="username"]').value;
// REAL-TIME VALIDATION
const emailInput = document.getElementById('email');
const emailError = document.getElementById('emailError');
emailInput.addEventListener('blur', () => {
if (!emailInput.value.includes('@')) {
emailError.textContent = 'Invalid email';
emailInput.classList.add('error');
} else {
emailError.textContent = '';
emailInput.classList.remove('error');
}
});
// Clear error on input
emailInput.addEventListener('input', () => {
emailError.textContent = '';
emailInput.classList.remove('error');
});
// PASSWORD STRENGTH INDICATOR
const passwordInput = document.getElementById('password');
const strengthMeter = document.getElementById('strength');
passwordInput.addEventListener('input', () => {
const password = passwordInput.value;
let strength = 'Weak';
if (password.length >= 8) {
strength = 'Medium';
}
if (password.length >= 12 && /[A-Z]/.test(password) && /[0-9]/.test(password)) {
strength = 'Strong';
}
strengthMeter.textContent = strength;
strengthMeter.className = strength.toLowerCase();
});
// SUBMIT VIA AJAX
async function submitForm(data) {
try {
const response = await fetch('/api/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (response.ok) {
showSuccess('Form submitted successfully!');
form.reset();
} else {
showError('Submission failed');
}
} catch (error) {
showError('Network error');
}
}
// RESET FORM
const resetBtn = document.getElementById('resetBtn');
resetBtn.addEventListener('click', () => {
form.reset(); // Clear all fields
clearErrors();
});
// PREVENT MULTIPLE SUBMISSIONS
let isSubmitting = false;
form.addEventListener('submit', async (event) => {
event.preventDefault();
if (isSubmitting) return; // Prevent double submit
isSubmitting = true;
const submitBtn = form.querySelector('button[type="submit"]');
submitBtn.disabled = true;
submitBtn.textContent = 'Submitting...';
await submitForm(new FormData(form));
isSubmitting = false;
submitBtn.disabled = false;
submitBtn.textContent = 'Submit';
});
// FILE INPUT HANDLING
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', (event) => {
const files = event.target.files;
if (files.length > 0) {
const file = files[0];
console.log('File:', file.name);
console.log('Size:', file.size);
console.log('Type:', file.type);
// Validate file
if (file.size > 5 * 1024 * 1024) {
alert('File too large (max 5MB)');
fileInput.value = '';
}
}
});163. What is localStorage in JavaScript?
Difficulty: EasyType: MCQTopic: Web Storage
- Local variables in functions
- Browser storage that persists data across sessions
- Temporary memory
- Server-side storage
LocalStorage is a web storage API that stores key-value pairs in the browser. Data persists even after closing the browser.
Data is stored as strings. Use JSON.stringify to store objects and JSON.parse to retrieve them.
Storage limit is about 5 to 10 MB depending on browser. Data is accessible only to the same origin.
LocalStorage is synchronous and blocks the main thread. SessionStorage is similar but clears on browser close.
Use localStorage for user preferences, settings, and non-sensitive data. Never store sensitive information like passwords.
Understanding browser storage is essential for creating stateful web applications.
Correct Answer: Browser storage that persists data across sessions
Example Code
// SET ITEM
localStorage.setItem('username', 'John');
localStorage.setItem('theme', 'dark');
// GET ITEM
const username = localStorage.getItem('username');
console.log(username); // 'John'
const theme = localStorage.getItem('theme');
console.log(theme); // 'dark'
// Non-existent key returns null
const missing = localStorage.getItem('missing');
console.log(missing); // null
// REMOVE ITEM
localStorage.removeItem('username');
// CLEAR ALL
localStorage.clear(); // Removes everything
// STORE OBJECTS (must stringify)
const user = {
name: 'John',
age: 30,
preferences: { theme: 'dark' }
};
// Store
localStorage.setItem('user', JSON.stringify(user));
// Retrieve
const storedUser = JSON.parse(localStorage.getItem('user'));
console.log(storedUser.name); // 'John'
// STORE ARRAY
const items = ['apple', 'banana', 'orange'];
localStorage.setItem('items', JSON.stringify(items));
const retrievedItems = JSON.parse(localStorage.getItem('items'));
console.log(retrievedItems); // ['apple', 'banana', 'orange']
// CHECK IF KEY EXISTS
if (localStorage.getItem('username') !== null) {
console.log('Username exists');
}
// GET ALL KEYS
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
console.log(key, localStorage.getItem(key));
}
// HELPER FUNCTIONS
// Safe get with default
function getFromStorage(key, defaultValue = null) {
const value = localStorage.getItem(key);
if (value === null) return defaultValue;
try {
return JSON.parse(value);
} catch {
return value;
}
}
// Safe set
function setInStorage(key, value) {
try {
localStorage.setItem(key, JSON.stringify(value));
return true;
} catch (error) {
console.error('Storage error:', error);
return false;
}
}
// PRACTICAL EXAMPLES
// Dark mode toggle
const toggleBtn = document.getElementById('themeToggle');
// Load saved theme
const savedTheme = localStorage.getItem('theme') || 'light';
document.body.classList.add(savedTheme);
toggleBtn.addEventListener('click', () => {
document.body.classList.toggle('dark');
const newTheme = document.body.classList.contains('dark') ? 'dark' : 'light';
localStorage.setItem('theme', newTheme);
});
// Save form progress
const form = document.getElementById('myForm');
const inputs = form.querySelectorAll('input, textarea');
// Save on input
inputs.forEach(input => {
// Load saved value
const saved = localStorage.getItem(input.name);
if (saved) input.value = saved;
// Save on change
input.addEventListener('input', () => {
localStorage.setItem(input.name, input.value);
});
});
// Clear on submit
form.addEventListener('submit', () => {
inputs.forEach(input => {
localStorage.removeItem(input.name);
});
});
// Shopping cart
function addToCart(item) {
const cart = JSON.parse(localStorage.getItem('cart')) || [];
cart.push(item);
localStorage.setItem('cart', JSON.stringify(cart));
}
function getCart() {
return JSON.parse(localStorage.getItem('cart')) || [];
}
function clearCart() {
localStorage.removeItem('cart');
}
// STORAGE EVENT (listen for changes in other tabs)
window.addEventListener('storage', (event) => {
console.log('Storage changed!');
console.log('Key:', event.key);
console.log('Old value:', event.oldValue);
console.log('New value:', event.newValue);
console.log('URL:', event.url);
});
// SESSION STORAGE (clears on browser close)
sessionStorage.setItem('sessionData', 'value');
const sessionData = sessionStorage.getItem('sessionData');
// STORAGE LIMITS
try {
// Test storage limit
const testKey = 'test';
const testValue = 'x'.repeat(10 * 1024 * 1024); // 10MB
localStorage.setItem(testKey, testValue);
} catch (error) {
console.error('Storage quota exceeded');
}164. Explain different ways to manipulate the DOM. Compare innerHTML, textContent, createElement, and when to use each.
Difficulty: MediumType: SubjectiveTopic: DOM Basics
JavaScript provides multiple methods for DOM manipulation, each with different use cases, performance, and security implications.
InnerHTML:
Gets or sets HTML content as a string. Parses HTML and creates elements. Fast for large replacements. Security risk with user input due to XSS attacks. Use for trusted HTML content or templates. Avoid for user-generated content.
TextContent:
Gets or sets text content without HTML parsing. Faster and safer than innerHTML. Includes hidden text. Use for plain text to prevent XSS. Best for user input display. No security concerns.
InnerText:
Gets visible text respecting CSS display. Triggers reflow, slower than textContent. Excludes hidden elements. Use when you need only visible text. Less common in practice.
CreateElement with appendChild:
Creates elements programmatically. Most secure and flexible. Better performance for single elements. Full control over attributes and properties. Use for dynamic element creation. Best for complex structures.
InsertAdjacentHTML:
Inserts HTML at specific position. Does not replace existing content. More efficient than innerHTML for additions. Same XSS risk as innerHTML. Use for adding content without replacing.
Document Fragment:
Creates off-screen container for multiple elements. Add many elements, then append once. Minimizes reflows and repaints. Best performance for bulk operations. Use when adding many elements.
When to use each:
Use textContent for user-generated text. Use createElement for programmatic element creation. Use innerHTML only for trusted HTML templates. Use document fragments for multiple elements. Always sanitize user input.
Performance considerations:
TextContent is fastest for text. InnerHTML is fast for bulk replacements. CreateElement is best for single elements. Document fragments optimize multiple additions.
Security best practices:
Never use innerHTML with user input. Always escape or sanitize user data. Prefer textContent or createElement. Use Content Security Policy. Validate on server side too.
Example Code
// innerHTML - HTML parsing
const container = document.getElementById('container');
// Set HTML content
container.innerHTML = '<p>Hello <strong>World</strong></p>';
// Creates actual <p> and <strong> elements
// Get HTML content
console.log(container.innerHTML);
// '<p>Hello <strong>World</strong></p>'
// SECURITY RISK with user input
const userInput = '<img src=x onerror="alert(\'XSS\')">';
// container.innerHTML = userInput; // DANGER! Executes script
// Safe alternative for user input
container.textContent = userInput; // Displays as text
// textContent - Plain text
// Set text (safe)
container.textContent = 'Plain text <b>not parsed</b>';
// Displays: Plain text <b>not parsed</b>
// Get all text (includes hidden)
const div = document.createElement('div');
div.innerHTML = 'Visible <span style="display:none">Hidden</span>';
console.log(div.textContent); // 'Visible Hidden'
console.log(div.innerText); // 'Visible' (excludes hidden)
// createElement - Programmatic creation
function createCard(title, description) {
const card = document.createElement('div');
card.className = 'card';
const heading = document.createElement('h3');
heading.textContent = title; // Safe
const para = document.createElement('p');
para.textContent = description; // Safe
const button = document.createElement('button');
button.textContent = 'Click';
button.addEventListener('click', () => {
console.log('Clicked:', title);
});
card.appendChild(heading);
card.appendChild(para);
card.appendChild(button);
return card;
}
container.appendChild(createCard('Title', 'Description'));
// PERFORMANCE COMPARISON
// Slow - multiple reflows
for (let i = 0; i < 100; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
list.appendChild(li); // Reflow each time!
}
// Fast - one reflow with fragment
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
fragment.appendChild(li);
}
list.appendChild(fragment); // One reflow!
// Fast - innerHTML for bulk
const html = Array.from({length: 100}, (_, i) =>
`<li>Item ${i}</li>`
).join('');
list.innerHTML = html;
// insertAdjacentHTML - Positioned insertion
const element = document.getElementById('target');
// Four positions:
element.insertAdjacentHTML('beforebegin', '<p>Before</p>');
element.insertAdjacentHTML('afterbegin', '<p>First child</p>');
element.insertAdjacentHTML('beforeend', '<p>Last child</p>');
element.insertAdjacentHTML('afterend', '<p>After</p>');
// REAL-WORLD EXAMPLES
// Example 1: Render user list (safe)
function renderUsers(users) {
const list = document.getElementById('userList');
list.innerHTML = ''; // Clear
users.forEach(user => {
const li = document.createElement('li');
li.className = 'user-item';
const name = document.createElement('span');
name.textContent = user.name; // Safe from XSS
const email = document.createElement('span');
email.textContent = user.email;
li.appendChild(name);
li.appendChild(email);
list.appendChild(li);
});
}
// Example 2: Template with safe interpolation
function createProductCard(product) {
const template = document.createElement('div');
template.className = 'product';
// Safe way to build HTML
const img = document.createElement('img');
img.src = product.image;
img.alt = product.name;
const title = document.createElement('h3');
title.textContent = product.name;
const price = document.createElement('p');
price.textContent = `$${product.price}`;
template.append(img, title, price);
return template;
}
// Example 3: Efficient list rendering
function renderLargeList(items) {
const fragment = document.createDocumentFragment();
items.forEach(item => {
const div = document.createElement('div');
div.textContent = item;
fragment.appendChild(div);
});
container.appendChild(fragment);
}
// WHEN TO USE WHAT
// Use textContent:
// - User input display
// - Plain text updates
// - Security-sensitive content
// Use innerHTML:
// - Trusted HTML templates
// - Bulk HTML generation
// - Performance-critical bulk updates
// Use createElement:
// - Single element creation
// - Complex element structures
// - When you need event listeners
// - Maximum security and control
// Use document fragment:
// - Adding multiple elements
// - Performance-critical operations
// - Reducing reflows165. Explain event bubbling, capturing, and event delegation. Provide practical examples and use cases.
Difficulty: HardType: SubjectiveTopic: DOM Events
Events in JavaScript propagate through the DOM in three phases: capturing, target, and bubbling. Understanding this is crucial for effective event handling.
Event capturing phase:
Event travels from root down to target element. Window to document to body to parent to target. Use third parameter true in addEventListener. Rarely used in practice. Useful for intercepting events before they reach target.
Target phase:
Event reaches the actual target element. Target's event handlers execute. This is where the event originated.
Bubbling phase:
Event bubbles up from target to root. Most event handlers work in bubble phase. This is the default behavior. Target to parent to body to document to window.
Event delegation pattern:
Attach single listener to parent element. Handle events from multiple children. Check event.target to identify which child triggered event. More efficient than many individual listeners. Automatically works for dynamically added elements.
Stopping propagation:
Event.stopPropagation stops bubbling to parents. Event.stopImmediatePropagation stops other handlers on same element. Use sparingly as it can break delegation patterns.
Event.target vs event.currentTarget:
Target is the element that triggered the event, the innermost clicked element. CurrentTarget is the element with the listener attached, the element currently handling the event. During bubbling, target stays same but currentTarget changes.
Practical use cases:
Event delegation for lists and tables. Click outside to close dropdowns. Modal background click to close. Navigation menus. Dynamic form fields. Infinite scroll lists.
Benefits of delegation:
Better performance with fewer listeners. Less memory usage. Works with dynamic content. Cleaner code with centralized handling. Easier maintenance.
When not to use delegation:
Events that do not bubble like focus and blur. Need specific this binding for each element. Very specific single-element behavior. Performance-critical handlers on many elements.
Understanding event propagation is essential for building efficient, maintainable event-driven applications.
Example Code
// EVENT PROPAGATION PHASES
const outer = document.getElementById('outer');
const middle = document.getElementById('middle');
const inner = document.getElementById('inner');
// Capture phase (true parameter)
outer.addEventListener('click', () => {
console.log('Outer CAPTURE');
}, true);
middle.addEventListener('click', () => {
console.log('Middle CAPTURE');
}, true);
inner.addEventListener('click', () => {
console.log('Inner CAPTURE');
}, true);
// Bubble phase (default)
outer.addEventListener('click', () => {
console.log('Outer BUBBLE');
});
middle.addEventListener('click', () => {
console.log('Middle BUBBLE');
});
inner.addEventListener('click', () => {
console.log('Inner BUBBLE');
});
// When inner clicked, output:
// Outer CAPTURE
// Middle CAPTURE
// Inner CAPTURE
// Inner BUBBLE
// Middle BUBBLE
// Outer BUBBLE
// EVENT DELEGATION - Todo List
const todoList = document.getElementById('todoList');
// One listener for all todos
todoList.addEventListener('click', (event) => {
const target = event.target;
// Delete button clicked
if (target.classList.contains('delete')) {
const li = target.closest('li');
li.remove();
}
// Checkbox clicked
if (target.type === 'checkbox') {
const li = target.closest('li');
li.classList.toggle('completed');
}
// Edit button clicked
if (target.classList.contains('edit')) {
const li = target.closest('li');
const span = li.querySelector('.text');
span.contentEditable = true;
span.focus();
}
});
// Add todos dynamically (delegation handles them automatically)
function addTodo(text) {
const li = document.createElement('li');
li.innerHTML = `
<input type="checkbox">
<span class="text">${text}</span>
<button class="edit">Edit</button>
<button class="delete">Delete</button>
`;
todoList.appendChild(li);
}
// EVENT DELEGATION - Table
const table = document.getElementById('dataTable');
table.addEventListener('click', (event) => {
const target = event.target;
// Find the row (even if clicked on cell)
const row = target.closest('tr');
if (row && row.dataset.id) {
console.log('Row clicked:', row.dataset.id);
// Check which column
const cell = target.closest('td');
if (cell) {
const columnIndex = cell.cellIndex;
console.log('Column:', columnIndex);
// Handle specific columns
if (columnIndex === 3) { // Actions column
if (target.classList.contains('edit')) {
editRow(row.dataset.id);
}
if (target.classList.contains('delete')) {
deleteRow(row.dataset.id);
}
}
}
}
});
// CLICK OUTSIDE TO CLOSE
const dropdown = document.getElementById('dropdown');
const button = document.getElementById('dropdownBtn');
// Open dropdown
button.addEventListener('click', (event) => {
event.stopPropagation(); // Don't trigger document listener
dropdown.classList.add('open');
});
// Close when clicking outside
document.addEventListener('click', (event) => {
if (!dropdown.contains(event.target)) {
dropdown.classList.remove('open');
}
});
// Don't close when clicking inside dropdown
dropdown.addEventListener('click', (event) => {
event.stopPropagation();
});
// MODAL WITH EVENT DELEGATION
const modal = document.getElementById('modal');
const modalContent = modal.querySelector('.modal-content');
// Close on background click
modal.addEventListener('click', (event) => {
// Only close if clicked directly on modal, not children
if (event.target === modal) {
closeModal();
}
});
// Alternative: check if clicked outside content
modal.addEventListener('click', (event) => {
if (!modalContent.contains(event.target)) {
closeModal();
}
});
// NAVIGATION MENU
const nav = document.getElementById('nav');
nav.addEventListener('click', (event) => {
const link = event.target.closest('a');
if (link) {
// Check if has submenu
const parent = link.parentElement;
if (parent.querySelector('.submenu')) {
event.preventDefault();
parent.classList.toggle('open');
}
}
});
// FORM WITH DELEGATION
const form = document.getElementById('dynamicForm');
// Handle all inputs with delegation
form.addEventListener('input', (event) => {
const input = event.target;
if (input.type === 'email') {
validateEmail(input);
}
if (input.type === 'password') {
validatePassword(input);
}
if (input.hasAttribute('data-validate')) {
validateField(input);
}
});
// Handle all buttons
form.addEventListener('click', (event) => {
if (event.target.matches('.add-field')) {
addField();
}
if (event.target.matches('.remove-field')) {
event.target.closest('.field').remove();
}
});
// PERFORMANCE COMPARISON
// Bad: Many listeners (memory intensive)
const items = document.querySelectorAll('.item');
items.forEach(item => {
item.addEventListener('click', handleClick);
});
// If 1000 items = 1000 listeners!
// Good: One listener with delegation
const container = document.getElementById('container');
container.addEventListener('click', (event) => {
if (event.target.classList.contains('item')) {
handleClick(event);
}
});
// Only 1 listener for 1000 items!
// STOPPING PROPAGATION
inner.addEventListener('click', (event) => {
console.log('Inner');
event.stopPropagation(); // Stops bubbling
// Outer handlers won't fire
});
// Stop other handlers on same element
button.addEventListener('click', (event) => {
console.log('First');
event.stopImmediatePropagation();
});
button.addEventListener('click', () => {
console.log('Second'); // Won't execute
});166. What will be the output?
const {a, b, c = 3} = {a: 1, b: 2};
console.log(c);
Difficulty: MediumType: MCQTopic: Modern Syntax
Destructuring with default values assigns the default when the property is undefined or does not exist.
In this case, the object does not have a c property, so c gets the default value of 3.
Default values only apply when the value is undefined, not when it is null or any other falsy value.
This pattern is useful for providing fallback values when destructuring objects.
Destructuring with defaults is commonly used in function parameters and API responses.
Understanding default values in destructuring prevents bugs from missing properties.
Correct Answer: 3
Example Code
// Object destructuring with defaults
const {a, b, c = 3} = {a: 1, b: 2};
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3 (default value)
// Default only used when undefined
const {x = 10} = {x: 0};
console.log(x); // 0 (not 10, because 0 is defined)
const {y = 10} = {y: null};
console.log(y); // null (not 10, because null is defined)
const {z = 10} = {z: undefined};
console.log(z); // 10 (undefined triggers default)
const {w = 10} = {};
console.log(w); // 10 (missing property)
// Array destructuring with defaults
const [first, second, third = 3] = [1, 2];
console.log(third); // 3
// Nested destructuring with defaults
const user = {
name: 'John',
address: {
city: 'NYC'
}
};
const {
name,
address: {city, zip = '00000'}
} = user;
console.log(city); // 'NYC'
console.log(zip); // '00000' (default)
// Function parameters with destructuring
function greet({name, greeting = 'Hello'}) {
console.log(`${greeting}, ${name}`);
}
greet({name: 'John'}); // 'Hello, John'
greet({name: 'Jane', greeting: 'Hi'}); // 'Hi, Jane'
// Common pattern in React
function Component({title, count = 0, onSubmit = () => {}}) {
// Use props with defaults
}167. What does the spread operator (...) do?
Difficulty: EasyType: MCQTopic: Modern Syntax
- Creates a loop
- Expands an iterable into individual elements
- Combines two functions
- Delays execution
The spread operator expands an iterable like an array or object into individual elements.
For arrays, it spreads elements for function arguments, copying, or combining arrays.
For objects, it spreads properties for copying or merging objects.
Spread creates shallow copies, not deep copies. Nested objects are still referenced.
It provides cleaner syntax than older methods like concat or Object.assign.
Spread is one of the most useful ES6 features for working with arrays and objects.
Correct Answer: Expands an iterable into individual elements
Example Code
// ARRAY SPREAD
// Copy array
const arr1 = [1, 2, 3];
const arr2 = [...arr1];
arr2.push(4);
console.log(arr1); // [1, 2, 3] (unchanged)
console.log(arr2); // [1, 2, 3, 4]
// Combine arrays
const nums1 = [1, 2];
const nums2 = [3, 4];
const combined = [...nums1, ...nums2];
console.log(combined); // [1, 2, 3, 4]
// Add elements while spreading
const items = [2, 3];
const extended = [1, ...items, 4, 5];
console.log(extended); // [1, 2, 3, 4, 5]
// Function arguments
const numbers = [5, 10, 15];
console.log(Math.max(...numbers)); // 15
// Same as: Math.max(5, 10, 15)
// String to array
const str = 'hello';
const chars = [...str];
console.log(chars); // ['h', 'e', 'l', 'l', 'o']
// OBJECT SPREAD
// Copy object
const user = {name: 'John', age: 30};
const userCopy = {...user};
userCopy.age = 31;
console.log(user.age); // 30 (unchanged)
// Merge objects
const defaults = {theme: 'light', lang: 'en'};
const settings = {theme: 'dark'};
const config = {...defaults, ...settings};
console.log(config); // {theme: 'dark', lang: 'en'}
// Later properties override earlier ones
// Add properties while spreading
const person = {name: 'John'};
const employee = {...person, role: 'Developer', id: 123};
console.log(employee);
// {name: 'John', role: 'Developer', id: 123}
// SHALLOW COPY WARNING
const original = {
name: 'John',
address: {city: 'NYC'}
};
const copy = {...original};
copy.address.city = 'LA';
console.log(original.address.city); // 'LA' (changed!)
// Nested objects are still referenced
// PRACTICAL EXAMPLES
// React state updates
const state = {count: 0, user: {name: 'John'}};
const newState = {...state, count: state.count + 1};
// Array operations
const todos = ['Task 1', 'Task 2'];
const withNew = [...todos, 'Task 3'];
const withoutFirst = todos.slice(1); // or [...todos.slice(1)]
// Remove item by index
const arr = [1, 2, 3, 4, 5];
const index = 2;
const removed = [...arr.slice(0, index), ...arr.slice(index + 1)];
console.log(removed); // [1, 2, 4, 5]168. What is the difference between rest parameters and the arguments object?
Difficulty: MediumType: MCQTopic: Modern Syntax
- No difference
- Rest parameters are a real array, arguments is array-like
- Arguments is faster
- Rest parameters only work in strict mode
Rest parameters collect all remaining arguments into a real array with all array methods available.
The arguments object is array-like but not a real array. It does not have array methods like map or filter.
Rest parameters must be the last parameter. Arguments object includes all parameters.
Arrow functions do not have arguments object, so rest parameters are the only way.
Rest parameters provide cleaner, more modern syntax than arguments.
Always prefer rest parameters in modern JavaScript code.
Correct Answer: Rest parameters are a real array, arguments is array-like
Example Code
// REST PARAMETERS - real array
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
console.log(sum(5, 10)); // 15
// numbers is a real array
function demo(...args) {
console.log(Array.isArray(args)); // true
console.log(args.map(x => x * 2)); // Works!
}
// Mixed parameters with rest
function greet(greeting, ...names) {
return `${greeting} ${names.join(' and ')}`;
}
console.log(greet('Hello', 'John', 'Jane', 'Bob'));
// 'Hello John and Jane and Bob'
// ARGUMENTS OBJECT - array-like
function oldWay() {
console.log(Array.isArray(arguments)); // false
// arguments.map(); // Error! No map method
// Convert to array
const arr = Array.from(arguments);
// or
const arr2 = [...arguments];
// or
const arr3 = Array.prototype.slice.call(arguments);
}
// Arrow functions have NO arguments object
const arrowFunc = () => {
// console.log(arguments); // ReferenceError!
};
// Must use rest parameters in arrow functions
const arrowSum = (...nums) => {
return nums.reduce((a, b) => a + b, 0);
};
// PRACTICAL EXAMPLES
// Logger with variable arguments
function log(level, ...messages) {
const timestamp = new Date().toISOString();
console.log(`[${level}] ${timestamp}:`, ...messages);
}
log('ERROR', 'Database error', 'Connection failed');
// Flexible calculator
function calculate(operation, ...nums) {
switch(operation) {
case 'add':
return nums.reduce((a, b) => a + b, 0);
case 'multiply':
return nums.reduce((a, b) => a * b, 1);
default:
return 0;
}
}
console.log(calculate('add', 1, 2, 3, 4)); // 10
console.log(calculate('multiply', 2, 3, 4)); // 24
// Destructuring with rest
const [first, second, ...rest] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(second); // 2
console.log(rest); // [3, 4, 5]
const {a, b, ...others} = {a: 1, b: 2, c: 3, d: 4};
console.log(others); // {c: 3, d: 4}
// Function accepting unlimited callbacks
function executeAll(...callbacks) {
callbacks.forEach(cb => cb());
}
executeAll(
() => console.log('First'),
() => console.log('Second'),
() => console.log('Third')
);169. When should you NOT use arrow functions?
Difficulty: MediumType: MCQTopic: Arrow Functions
- For array methods like map
- For object methods that need this
- For callbacks
- For pure functions
Arrow functions should not be used as object methods when you need this to refer to the object.
Arrow functions do not have their own this binding. They inherit this from the parent scope lexically.
Object methods need this to reference the object. Arrow functions will not work correctly here.
Also avoid arrow functions as constructors, they cannot be used with new. They do not have prototype property.
Do not use arrow functions when you need the arguments object.
Understanding when not to use arrow functions prevents common bugs with this binding.
Correct Answer: For object methods that need this
Example Code
// WHEN NOT TO USE ARROW FUNCTIONS
// 1. Object methods (this problem)
const person = {
name: 'John',
// Wrong - arrow function
greetArrow: () => {
console.log('Hello, ' + this.name); // undefined!
// this refers to parent scope (window), not person
},
// Right - regular function
greetRegular: function() {
console.log('Hello, ' + this.name); // 'Hello, John'
},
// Right - shorthand method
greetShort() {
console.log('Hello, ' + this.name); // 'Hello, John'
}
};
person.greetArrow(); // 'Hello, undefined'
person.greetRegular(); // 'Hello, John'
person.greetShort(); // 'Hello, John'
// 2. Cannot use as constructor
const Person = (name) => {
this.name = name;
};
// const john = new Person('John'); // TypeError!
// 3. No arguments object
const func = () => {
// console.log(arguments); // ReferenceError!
};
// Use rest parameters instead
const funcRest = (...args) => {
console.log(args); // Works!
};
// 4. Event handlers that need this
const button = document.getElementById('btn');
// Wrong - arrow function
button.addEventListener('click', () => {
console.log(this); // window, not button!
});
// Right - regular function
button.addEventListener('click', function() {
console.log(this); // button element
});
// WHEN TO USE ARROW FUNCTIONS
// 1. Array methods (this not needed)
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);
const evens = numbers.filter(n => n % 2 === 0);
// 2. Callbacks that need parent this
const timer = {
seconds: 0,
start: function() {
// Arrow function inherits this from start()
setInterval(() => {
this.seconds++; // this = timer
console.log(this.seconds);
}, 1000);
}
};
// 3. Short functions
const add = (a, b) => a + b;
const square = x => x * x;
// 4. Promise chains
fetch('/api/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
// MIXED EXAMPLE - Class with proper usage
class Component {
constructor() {
this.state = {count: 0};
}
// Regular method (can be called with new, has prototype)
render() {
return `Count: ${this.state.count}`;
}
// Arrow function as class field (binds this)
handleClick = () => {
this.state.count++; // this always refers to instance
}
// Using arrow in regular method for callback
setupTimer() {
setInterval(() => {
this.state.count++; // this = instance, not window
}, 1000);
}
}
// COMPARISON
// Traditional function with bind
class OldWay {
constructor() {
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log(this);
}
}
// Modern way with arrow function
class NewWay {
handleClick = () => {
console.log(this); // Always bound to instance
}
}170. What is the output?
Promise.resolve(1)
.then(x => x + 1)
.then(x => { throw new Error('error'); })
.catch(() => 1)
.then(x => x + 1)
.then(x => console.log(x));
Difficulty: HardType: MCQTopic: Promises
Promise chains execute in sequence. Let's trace through:
Start with Promise.resolve(1), value is 1.
First then: x plus 1 equals 2.
Second then: throws error, skips to catch.
Catch: returns 1, promise resolves with 1.
Next then: x plus 1 equals 2.
Final then: logs 2.
The catch handles the error and returns a value, so the chain continues normally.
After a catch, subsequent then blocks execute unless catch throws again.
This demonstrates error recovery in promise chains.
Correct Answer: 2
Example Code
// Tracing the promise chain
Promise.resolve(1) // 1
.then(x => x + 1) // 2
.then(x => { throw new Error(); }) // Error thrown
.catch(() => 1) // Caught, returns 1
.then(x => x + 1) // 2
.then(x => console.log(x)); // Logs: 2
// ERROR HANDLING IN CHAINS
// Error skips to next catch
Promise.resolve(1)
.then(x => { throw 'error'; })
.then(x => console.log('Skipped')) // Skipped
.catch(err => console.log('Caught')) // Caught
.then(() => console.log('Continues')); // Continues
// Catch returns value, chain continues
Promise.reject('error')
.catch(err => 'recovered')
.then(val => console.log(val)); // 'recovered'
// Catch throws, goes to next catch
Promise.reject('error1')
.catch(err => { throw 'error2'; })
.catch(err => console.log(err)); // 'error2'
// Multiple catches
Promise.reject('error')
.catch(err => {
console.log('First catch');
throw err; // Rethrow
})
.catch(err => {
console.log('Second catch');
});
// PROMISE METHODS
// Promise.all - all must succeed
Promise.all([Promise.resolve(1), Promise.resolve(2)])
.then(results => console.log(results)); // [1, 2]
Promise.all([Promise.resolve(1), Promise.reject('error')])
.catch(err => console.log(err)); // 'error'
// Promise.race - first to finish
Promise.race([
new Promise(resolve => setTimeout(() => resolve(1), 100)),
new Promise(resolve => setTimeout(() => resolve(2), 50))
]).then(result => console.log(result)); // 2
// Promise.allSettled - wait for all
Promise.allSettled([
Promise.resolve(1),
Promise.reject('error')
]).then(results => console.log(results));
// [{status: 'fulfilled', value: 1},
// {status: 'rejected', reason: 'error'}]
// Promise.any - first fulfilled
Promise.any([
Promise.reject('error1'),
Promise.resolve(2),
Promise.resolve(3)
]).then(result => console.log(result)); // 2
// CHAINING PATTERNS
// Return promise from then
fetch('/api/user')
.then(response => response.json()) // Returns promise
.then(user => fetch(`/api/posts/${user.id}`))
.then(response => response.json())
.then(posts => console.log(posts));
// Return value from then
Promise.resolve(5)
.then(x => x * 2) // Returns value, wrapped in promise
.then(x => x + 1)
.then(x => console.log(x)); // 11
// Finally block (runs regardless)
Promise.resolve(1)
.then(x => x + 1)
.catch(err => console.log(err))
.finally(() => console.log('Cleanup')); // Always runs171. Can you use await outside an async function?
Difficulty: MediumType: MCQTopic: Async Await
- Yes, always
- No, never
- Yes, at the top level in modules
- Yes, but only in strict mode
Await can be used at the top level in ES modules, introduced in ES2022.
Traditionally, await could only be used inside async functions.
Top-level await allows modules to act like async functions. Useful for dynamic imports and async initialization.
Other modules importing this module will wait for the await to complete.
In regular scripts or inside functions, await still requires async.
Top-level await simplifies module initialization but should be used carefully as it blocks module loading.
Correct Answer: Yes, at the top level in modules
Example Code
// TOP-LEVEL AWAIT (ES2022 modules)
// module.js
const data = await fetch('/api/data').then(r => r.json());
export default data;
// Other modules importing this wait for fetch to complete
// Traditional way (still needed in non-modules)
(async () => {
const data = await fetch('/api/data');
console.log(data);
})();
// ASYNC FUNCTION REQUIRED
async function fetchData() {
const response = await fetch('/api/data');
const data = await response.json();
return data;
}
// Error: await without async
function wrong() {
// const data = await fetch('/api'); // SyntaxError!
}
// PRACTICAL EXAMPLES
// Sequential awaits
async function sequential() {
const user = await fetchUser(); // Wait
const posts = await fetchPosts(); // Then wait
const comments = await fetchComments(); // Then wait
return {user, posts, comments};
}
// Total time: time1 + time2 + time3
// Parallel awaits
async function parallel() {
const [user, posts, comments] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchComments()
]);
return {user, posts, comments};
}
// Total time: max(time1, time2, time3)
// Error handling
async function withErrorHandling() {
try {
const data = await fetch('/api/data');
return data.json();
} catch (error) {
console.error('Error:', error);
return null;
}
}
// Multiple try-catch blocks
async function multipleTryCatch() {
try {
const user = await fetchUser();
} catch (error) {
console.error('User fetch failed');
}
try {
const posts = await fetchPosts();
} catch (error) {
console.error('Posts fetch failed');
}
}
// Await in loops
async function processItems(items) {
// Sequential processing
for (const item of items) {
await processItem(item); // Wait for each
}
// Parallel processing
await Promise.all(items.map(item => processItem(item)));
}
// Conditional await
async function conditionalAwait(useCache) {
if (useCache) {
return getCachedData();
} else {
return await fetchFreshData(); // Only await if needed
}
}
// TOP-LEVEL AWAIT USE CASES
// Dynamic imports
const module = await import('./module.js');
// Database connection
const db = await connectDatabase();
// Feature detection
const isSupported = await checkFeatureSupport();
// Config loading
const config = await fetch('/config.json').then(r => r.json());
// Resource initialization
const resources = await loadResources();172. What is the main difference between Map and Object?
Difficulty: MediumType: MCQTopic: Map Set
- No difference
- Map allows any type as key, Object only strings and symbols
- Map is faster for all operations
- Object is newer than Map
Map allows any data type as keys including objects, functions, and primitives. Objects only allow strings and symbols as keys.
Map maintains insertion order of keys. Objects do not guarantee order, though modern engines maintain it.
Map has a size property. Objects need Object.keys for length.
Map performs better for frequent additions and deletions. Objects are optimized for static property access.
Map is iterable by default with forEach, for of. Objects need Object.keys or Object.entries.
Use Map for key-value storage with non-string keys or when order matters.
Correct Answer: Map allows any type as key, Object only strings and symbols
Example Code
// MAP - any type as key
const map = new Map();
// Object as key
const keyObj = {id: 1};
map.set(keyObj, 'value');
// Function as key
const keyFunc = () => {};
map.set(keyFunc, 'function value');
// Primitive as key
map.set(1, 'number key');
map.set('name', 'string key');
console.log(map.get(keyObj)); // 'value'
console.log(map.get(1)); // 'number key'
// OBJECT - only string/symbol keys
const obj = {};
obj[keyObj] = 'value'; // Converts to '[object Object]'
obj[1] = 'value'; // Converts to '1'
console.log(obj[keyObj]); // 'value'
console.log(obj['[object Object]']); // 'value' (same!)
// MAP METHODS
const map2 = new Map();
// Set value
map2.set('name', 'John');
map2.set('age', 30);
// Get value
console.log(map2.get('name')); // 'John'
// Check if key exists
console.log(map2.has('name')); // true
// Delete key
map2.delete('age');
// Size
console.log(map2.size); // 1
// Clear all
map2.clear();
// ITERATION
const map3 = new Map([
['a', 1],
['b', 2],
['c', 3]
]);
// forEach
map3.forEach((value, key) => {
console.log(key, value);
});
// for...of
for (const [key, value] of map3) {
console.log(key, value);
}
// Keys, values, entries
console.log([...map3.keys()]); // ['a', 'b', 'c']
console.log([...map3.values()]); // [1, 2, 3]
console.log([...map3.entries()]); // [['a',1], ['b',2], ['c',3]]
// SET - unique values
const set = new Set();
set.add(1);
set.add(2);
set.add(2); // Duplicate ignored
set.add(3);
console.log(set.size); // 3
console.log(set.has(2)); // true
set.delete(2);
set.clear();
// Array to Set (remove duplicates)
const arr = [1, 2, 2, 3, 3, 4];
const uniqueSet = new Set(arr);
const uniqueArr = [...uniqueSet];
console.log(uniqueArr); // [1, 2, 3, 4]
// PRACTICAL EXAMPLES
// Cache with Map
const cache = new Map();
function memoizedFunction(key) {
if (cache.has(key)) {
return cache.get(key);
}
const result = expensiveOperation(key);
cache.set(key, result);
return result;
}
// DOM elements as keys
const elementData = new Map();
const button = document.getElementById('btn');
elementData.set(button, {clicks: 0, lastClick: null});
// Unique values with Set
const usernames = new Set();
usernames.add('john');
usernames.add('jane');
if (usernames.has('john')) {
console.log('Username taken');
}
// WEAKMAP and WEAKSET
// WeakMap - keys are objects, garbage collected
const weakMap = new WeakMap();
let obj2 = {id: 1};
weakMap.set(obj2, 'data');
obj2 = null; // Object can be garbage collected
// WeakSet - values are objects, garbage collected
const weakSet = new WeakSet();
let obj3 = {id: 1};
weakSet.add(obj3);
obj3 = null; // Object can be garbage collected173. What is unique about Symbol?
Difficulty: HardType: MCQTopic: Data Types
- It is the only primitive that is mutable
- Every Symbol is unique, even with same description
- Symbols can be used in arithmetic
- Symbols are faster than strings
Every Symbol is guaranteed to be unique, even if created with the same description.
Symbols are primitive values used as unique property keys. They prevent property name collisions.
Symbol properties are not enumerable in for in or Object.keys. They are hidden from normal iteration.
Use Symbol.for to create global symbols that can be retrieved later.
Symbols are commonly used for private-like properties and defining well-known symbols.
Understanding Symbols shows advanced JavaScript knowledge of primitive types and metaprogramming.
Correct Answer: Every Symbol is unique, even with same description
Example Code
// SYMBOL UNIQUENESS
const sym1 = Symbol('description');
const sym2 = Symbol('description');
console.log(sym1 === sym2); // false (always unique!)
// Each Symbol is unique
const id1 = Symbol('id');
const id2 = Symbol('id');
const user = {
[id1]: 123,
[id2]: 456
};
console.log(user[id1]); // 123
console.log(user[id2]); // 456
// Different properties!
// HIDDEN PROPERTIES
const obj = {
name: 'John',
age: 30
};
const secretId = Symbol('id');
obj[secretId] = 12345;
// Not in for...in
for (let key in obj) {
console.log(key); // Only 'name' and 'age'
}
// Not in Object.keys
console.log(Object.keys(obj)); // ['name', 'age']
// Not in JSON.stringify
console.log(JSON.stringify(obj)); // {"name":"John","age":30}
// But accessible
console.log(obj[secretId]); // 12345
// Get symbols
const symbols = Object.getOwnPropertySymbols(obj);
console.log(symbols); // [Symbol(id)]
// GLOBAL SYMBOLS
// Create or get global symbol
const globalSym1 = Symbol.for('app.id');
const globalSym2 = Symbol.for('app.id');
console.log(globalSym1 === globalSym2); // true (same symbol!)
// Get key from symbol
console.log(Symbol.keyFor(globalSym1)); // 'app.id'
// WELL-KNOWN SYMBOLS
// Symbol.iterator - make object iterable
const range = {
from: 1,
to: 5,
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next() {
if (current <= last) {
return {value: current++, done: false};
} else {
return {done: true};
}
}
};
}
};
for (let num of range) {
console.log(num); // 1, 2, 3, 4, 5
}
// Symbol.toStringTag - customize toString
const myObject = {
[Symbol.toStringTag]: 'MyCustomClass'
};
console.log(myObject.toString()); // '[object MyCustomClass]'
// Symbol.hasInstance - customize instanceof
class MyArray {
static [Symbol.hasInstance](instance) {
return Array.isArray(instance);
}
}
console.log([1, 2] instanceof MyArray); // true
// PRACTICAL USE CASES
// Private properties (before # syntax)
const _private = Symbol('private');
class BankAccount {
constructor(balance) {
this[_private] = balance;
}
getBalance() {
return this[_private];
}
}
const account = new BankAccount(1000);
console.log(account.getBalance()); // 1000
// console.log(account._private); // undefined (hidden)
// Unique constants
const COLOR_RED = Symbol('red');
const COLOR_GREEN = Symbol('green');
const COLOR_BLUE = Symbol('blue');
function getColor(color) {
switch(color) {
case COLOR_RED:
return '#FF0000';
case COLOR_GREEN:
return '#00FF00';
case COLOR_BLUE:
return '#0000FF';
}
}
// Metadata storage
const metadata = Symbol('metadata');
function addMetadata(obj, data) {
obj[metadata] = data;
}
const item = {name: 'Product'};
addMetadata(item, {created: Date.now()});
console.log(item[metadata]); // {created: ...}
console.log(Object.keys(item)); // ['name'] (metadata hidden)174. What does obj?.prop?.nested return if obj is null?
Difficulty: EasyType: MCQTopic: Modern Syntax
Optional chaining returns undefined if any part of the chain is null or undefined.
It short-circuits and stops evaluation immediately when encountering null or undefined.
Optional chaining prevents TypeError when accessing properties of null or undefined.
It works with properties, array elements, and function calls.
Optional chaining makes code safer and cleaner than multiple existence checks.
Understanding optional chaining is essential for modern defensive JavaScript.
Correct Answer: undefined
Example Code
// OPTIONAL CHAINING BASICS
const obj = null;
console.log(obj?.prop?.nested); // undefined (no error)
// Without optional chaining
// console.log(obj.prop.nested); // TypeError!
// Traditional way
const value1 = obj && obj.prop && obj.prop.nested;
// PROPERTY ACCESS
const user = {
name: 'John',
address: {
city: 'NYC'
}
};
console.log(user?.name); // 'John'
console.log(user?.address?.city); // 'NYC'
console.log(user?.address?.zip); // undefined
console.log(user?.contact?.email); // undefined
const emptyUser = null;
console.log(emptyUser?.name); // undefined (not error)
// ARRAY ACCESS
const arr = [1, 2, 3];
console.log(arr?.[0]); // 1
console.log(arr?.[10]); // undefined
const nullArr = null;
console.log(nullArr?.[0]); // undefined
// FUNCTION CALLS
const obj2 = {
greet: () => 'Hello'
};
console.log(obj2.greet?.()); // 'Hello'
console.log(obj2.missing?.()); // undefined (no error)
const nullObj = null;
console.log(nullObj.method?.()); // undefined
// COMBINING WITH NULLISH COALESCING
const user2 = null;
const name = user2?.name ?? 'Guest';
console.log(name); // 'Guest'
const city = user2?.address?.city ?? 'Unknown';
console.log(city); // 'Unknown'
// PRACTICAL EXAMPLES
// API response handling
const response = await fetch('/api/user');
const data = await response.json();
const email = data?.user?.profile?.email ?? 'No email';
// Event handler
button?.addEventListener?.('click', handleClick);
// Nested object access
const config = {
server: {
host: 'localhost',
port: 3000
}
};
const port = config?.server?.port ?? 8080;
const ssl = config?.server?.ssl?.enabled ?? false;
// Array of objects
const users = [
{name: 'John', address: {city: 'NYC'}},
{name: 'Jane'},
null
];
users.forEach(user => {
console.log(user?.address?.city ?? 'No city');
});
// 'NYC', 'No city', 'No city'
// Method chaining
const result = obj
?.getData?.()
?.filter?.(x => x > 0)
?.[0];
// DELETE with optional chaining
delete user?.address?.street; // Safe
// WHEN NOT TO USE
// Don't overuse
const bad = data?.prop1?.prop2?.prop3?.prop4?.prop5;
// This might hide design issues
// Use with intention
if (user?.address) {
// We know user and address exist here
console.log(user.address.city); // No need for ?
}
// COMPARISON
// Old way - repetitive
const city1 = user && user.address && user.address.city;
// New way - clean
const city2 = user?.address?.city;
// Old way - try-catch
try {
const email1 = user.contact.email;
} catch {
const email1 = null;
}
// New way - optional chaining
const email2 = user?.contact?.email ?? null;175. What is the difference between named exports and default exports?
Difficulty: MediumType: MCQTopic: JS Modules
- No difference
- Named exports can have multiple, default export is one per module
- Default exports are faster
- Named exports are deprecated
A module can have multiple named exports but only one default export.
Named exports use the exact name when importing. They must be imported with curly braces.
Default export can be imported with any name without curly braces.
Named exports are better for exporting multiple items. Default exports are convenient for main module export.
You can mix named and default exports in the same module.
Understanding exports is essential for modular JavaScript development.
Correct Answer: Named exports can have multiple, default export is one per module
Example Code
// NAMED EXPORTS
// math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
export const multiply = (a, b) => a * b;
// Or export all at once
const divide = (a, b) => a / b;
const power = (a, b) => a ** b;
export {divide, power};
// Importing named exports (must use exact names)
import {add, subtract} from './math.js';
import {add as addition} from './math.js'; // Rename
// Import all named exports
import * as math from './math.js';
console.log(math.add(1, 2));
// DEFAULT EXPORT
// user.js
class User {
constructor(name) {
this.name = name;
}
}
export default User;
// Or inline
export default class User {
constructor(name) {
this.name = name;
}
}
// Function default export
export default function greet(name) {
return `Hello ${name}`;
}
// Importing default export (any name)
import User from './user.js';
import MyUser from './user.js'; // Can rename
import greetFunction from './greet.js';
// MIXING BOTH
// utils.js
export const PI = 3.14159;
export function square(x) {
return x * x;
}
class Calculator {
// Main export
}
export default Calculator;
// Importing mixed exports
import Calculator, {PI, square} from './utils.js';
// Default first, then named in {}
// EXPORT PATTERNS
// Re-exporting
// index.js
export {default as User} from './User.js';
export {add, subtract} from './math.js';
export * from './helpers.js';
// Aggregating exports
// api/index.js
export {fetchUsers} from './users.js';
export {fetchPosts} from './posts.js';
export {fetchComments} from './comments.js';
// Now can import all from one place
import {fetchUsers, fetchPosts} from './api/index.js';
// PRACTICAL EXAMPLES
// constants.js
export const API_URL = 'https://api.example.com';
export const TIMEOUT = 5000;
export const MAX_RETRIES = 3;
// config.js
const config = {
apiUrl: 'https://api.example.com',
timeout: 5000
};
export default config;
// Multiple utilities
// utils.js
export const formatDate = (date) => {
return date.toLocaleDateString();
};
export const formatCurrency = (amount) => {
return `$${amount.toFixed(2)}`;
};
export const capitalize = (str) => {
return str.charAt(0).toUpperCase() + str.slice(1);
};
// Main class with helpers
// Database.js
export class Database {
connect() {}
}
export const query = (sql) => {};
export const close = () => {};
// Or
class Database {
connect() {}
}
const query = (sql) => {};
const close = () => {};
export default Database;
export {query, close};
// IMPORT PATTERNS
// Selective import
import {add, subtract} from './math.js';
// Import all as namespace
import * as Math from './math.js';
Math.add(1, 2);
// Dynamic import (async)
const module = await import('./heavy-module.js');
module.default();
// Conditional import
if (condition) {
const {feature} = await import('./feature.js');
feature();
}
// BEST PRACTICES
// Prefer named exports for utilities
export const helper1 = () => {};
export const helper2 = () => {};
// Use default for main class/component
export default class Component {}
// Avoid mixing too much
// Either many named exports OR one default
// Not both unless necessary176. Explain destructuring in JavaScript. Cover object and array destructuring with advanced patterns.
Difficulty: MediumType: SubjectiveTopic: Modern Syntax
Destructuring is a syntax for extracting values from arrays or properties from objects into distinct variables in one statement.
Array destructuring:
Extract elements by position using square brackets. Can skip elements with empty commas. Use rest operator to collect remaining elements. Supports default values for missing elements. Works with any iterable including strings.
Object destructuring:
Extract properties by name using curly braces. Can rename variables with colon syntax. Supports default values for undefined properties. Can destructure nested objects. Use rest operator to collect remaining properties.
Advanced patterns:
Nested destructuring for deeply nested data. Destructuring in function parameters for clean APIs. Combining with default values for robust code. Using computed property names. Destructuring from function returns.
Common use cases:
Swapping variables without temp. Extracting values from API responses. Function parameters with options object. Working with arrays of data. Multiple return values from functions.
Benefits:
Cleaner code with less repetition. More readable variable extraction. Natural fit for modern JavaScript patterns. Essential for React and modern frameworks.
Gotchas:
Destructuring undefined or null throws error. Default values only work with undefined, not null. Creating new object when destructuring requires parentheses. Variable must be in scope for assignment destructuring.
Understanding destructuring thoroughly shows mastery of modern JavaScript syntax and patterns.
Example Code
// ARRAY DESTRUCTURING
const arr = [1, 2, 3, 4, 5];
// Basic extraction
const [first, second] = arr;
console.log(first); // 1
console.log(second); // 2
// Skip elements
const [a, , c] = arr;
console.log(a); // 1
console.log(c); // 3
// Rest operator
const [head, ...tail] = arr;
console.log(head); // 1
console.log(tail); // [2, 3, 4, 5]
// Default values
const [x, y, z = 10] = [1, 2];
console.log(z); // 10
// Swapping variables
let num1 = 10;
let num2 = 20;
[num1, num2] = [num2, num1];
console.log(num1); // 20
console.log(num2); // 10
// Nested arrays
const nested = [1, [2, 3], 4];
const [n1, [n2, n3]] = nested;
console.log(n2); // 2
// String destructuring
const [char1, char2] = 'hello';
console.log(char1); // 'h'
// OBJECT DESTRUCTURING
const user = {
name: 'John',
age: 30,
email: 'john@example.com'
};
// Basic extraction
const {name, age} = user;
console.log(name); // 'John'
console.log(age); // 30
// Rename variables
const {name: userName, age: userAge} = user;
console.log(userName); // 'John'
// Default values
const {email, phone = 'N/A'} = user;
console.log(phone); // 'N/A'
// Rest operator
const {name: n, ...others} = user;
console.log(others); // {age: 30, email: '...'}
// Nested objects
const person = {
name: 'John',
address: {
city: 'NYC',
zip: '10001'
}
};
const {
name: personName,
address: {city, zip}
} = person;
console.log(city); // 'NYC'
// FUNCTION PARAMETERS
// Object destructuring in params
function greet({name, age, country = 'USA'}) {
console.log(`${name}, ${age}, from ${country}`);
}
greet({name: 'John', age: 30});
// 'John, 30, from USA'
// Array destructuring in params
function sum([a, b]) {
return a + b;
}
console.log(sum([5, 3])); // 8
// ADVANCED PATTERNS
// Computed property names
const key = 'name';
const {[key]: value} = {name: 'John'};
console.log(value); // 'John'
// Destructure and keep original
const data = {x: 1, y: 2};
const {x, y, ...copy} = data;
console.log(copy); // {x: 1, y: 2}
// Multiple returns from function
function getUser() {
return {
name: 'John',
age: 30,
email: 'john@example.com'
};
}
const {name: n2, email: e} = getUser();
// Array method returns
function getCoordinates() {
return [10, 20];
}
const [x2, y2] = getCoordinates();
// PRACTICAL EXAMPLES
// API response
const response = {
data: {
user: {
id: 1,
name: 'John',
profile: {
avatar: 'url',
bio: 'text'
}
}
},
status: 200
};
const {
data: {
user: {
name: userName2,
profile: {avatar}
}
},
status
} = response;
console.log(userName2, avatar, status);
// React props
function Component({title, count = 0, onClick}) {
return `${title}: ${count}`;
}
// Array methods
const users = [
{name: 'John', age: 30},
{name: 'Jane', age: 25}
];
users.forEach(({name, age}) => {
console.log(name, age);
});
const names = users.map(({name}) => name);
// For...of with destructuring
for (const {name, age} of users) {
console.log(name, age);
}
// Object.entries
const obj = {a: 1, b: 2, c: 3};
for (const [key, value] of Object.entries(obj)) {
console.log(key, value);
}
// GOTCHAS
// Error: destructuring undefined/null
// const {x} = undefined; // TypeError!
// const {y} = null; // TypeError!
// Safe with default
const {x: x3} = undefined || {}; // OK
// Assignment destructuring needs parentheses
let name2, age2;
({name: name2, age: age2} = {name: 'John', age: 30});
// Parentheses required!
// Default only for undefined
const {val = 10} = {val: 0};
console.log(val); // 0 (not 10)
const {val2 = 10} = {val2: null};
console.log(val2); // null (not 10)177. Compare Promises and async/await. Explain error handling, parallel execution, and best practices for each.
Difficulty: HardType: SubjectiveTopic: Promises
Promises and async await are two ways to handle asynchronous operations in JavaScript. Async await is built on Promises and provides cleaner syntax.
Promises:
Represent eventual completion or failure of async operation. Use then for success, catch for errors, finally for cleanup. Can chain multiple operations with then. Promise.all for parallel execution. Promise.race for first completed. More explicit about async nature.
Async Await:
Syntactic sugar over Promises. Makes async code look synchronous. Use try catch for error handling. Easier to read and debug. Can use await in loops naturally. Top-level await in modules.
Error handling:
Promises use catch method or second argument to then. Async await uses try catch blocks. Catch can handle errors from multiple thens. Try catch can wrap multiple awaits. Finally block works for cleanup in both.
Parallel execution:
Promises naturally support Promise.all for concurrent operations. Async await needs Promise.all explicitly. Sequential awaits are slower. Always use Promise.all for independent operations. Promise.allSettled waits for all regardless of failures.
When to use Promises:
Complex chaining with transformations. Multiple independent operations. When you need promise objects. Working with Promise combinators. Callback-based APIs with promisify.
When to use async await:
Linear flow of operations. Better readability needed. Using with loops. Complex try catch needed. Most new code.
Best practices:
Always handle errors with catch or try catch. Use Promise.all for parallel operations. Avoid sequential awaits for independent operations. Return promises from async functions when appropriate. Use Promise.allSettled when all results needed. Name async functions descriptively.
Common mistakes:
Forgetting to await or return promises. Sequential awaits causing slow code. Not handling promise rejections. Mixing callbacks with promises. Using async without await unnecessarily.
Understanding both approaches and when to use each shows mastery of asynchronous JavaScript.
Example Code
// PROMISES
// Basic promise
function fetchUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id > 0) {
resolve({id, name: 'John'});
} else {
reject('Invalid ID');
}
}, 1000);
});
}
// Using promises
fetchUser(1)
.then(user => {
console.log('User:', user);
return fetchPosts(user.id);
})
.then(posts => {
console.log('Posts:', posts);
return fetchComments(posts[0].id);
})
.then(comments => {
console.log('Comments:', comments);
})
.catch(error => {
console.error('Error:', error);
})
.finally(() => {
console.log('Cleanup');
});
// ASYNC/AWAIT
async function getUserData(id) {
try {
const user = await fetchUser(id);
console.log('User:', user);
const posts = await fetchPosts(user.id);
console.log('Posts:', posts);
const comments = await fetchComments(posts[0].id);
console.log('Comments:', comments);
return {user, posts, comments};
} catch (error) {
console.error('Error:', error);
throw error; // Re-throw if needed
} finally {
console.log('Cleanup');
}
}
// PARALLEL EXECUTION
// Promises - Promise.all
Promise.all([
fetchUser(1),
fetchPosts(1),
fetchComments(1)
])
.then(([user, posts, comments]) => {
console.log({user, posts, comments});
})
.catch(error => {
console.error('One failed:', error);
});
// Async/await - sequential (SLOW)
async function sequentialSlow() {
const user = await fetchUser(1); // Wait
const posts = await fetchPosts(1); // Then wait
const comments = await fetchComments(1); // Then wait
return {user, posts, comments};
}
// Total time: sum of all
// Async/await - parallel (FAST)
async function parallelFast() {
const [user, posts, comments] = await Promise.all([
fetchUser(1),
fetchPosts(1),
fetchComments(1)
]);
return {user, posts, comments};
}
// Total time: max of all
// Start promises, then await
async function parallelPattern() {
const userPromise = fetchUser(1);
const postsPromise = fetchPosts(1);
const commentsPromise = fetchComments(1);
const user = await userPromise;
const posts = await postsPromise;
const comments = await commentsPromise;
return {user, posts, comments};
}
// ERROR HANDLING COMPARISON
// Promises - catch chain
fetchUser(1)
.then(user => fetchPosts(user.id))
.then(posts => fetchComments(posts[0].id))
.catch(error => {
// Catches any error in the chain
console.error(error);
});
// Async/await - try/catch
async function withErrorHandling() {
try {
const user = await fetchUser(1);
const posts = await fetchPosts(user.id);
const comments = await fetchComments(posts[0].id);
} catch (error) {
// Catches any error
console.error(error);
}
}
// Multiple try/catch for specific handling
async function specificErrorHandling() {
let user, posts;
try {
user = await fetchUser(1);
} catch (error) {
console.error('User fetch failed:', error);
return null;
}
try {
posts = await fetchPosts(user.id);
} catch (error) {
console.error('Posts fetch failed:', error);
posts = [];
}
return {user, posts};
}
// PROMISE COMBINATORS
// Promise.all - all must succeed
Promise.all([promise1, promise2, promise3])
.then(results => console.log(results))
.catch(err => console.log('One failed'));
// Promise.allSettled - wait for all
Promise.allSettled([promise1, promise2, promise3])
.then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('Success:', result.value);
} else {
console.log('Failed:', result.reason);
}
});
});
// Promise.race - first to finish
Promise.race([promise1, promise2])
.then(result => console.log('First:', result));
// Promise.any - first fulfilled
Promise.any([promise1, promise2, promise3])
.then(result => console.log('First success:', result));
// PRACTICAL PATTERNS
// Retry logic with promises
function retry(fn, retries = 3) {
return fn().catch(err => {
if (retries > 0) {
return retry(fn, retries - 1);
}
throw err;
});
}
// Retry logic with async/await
async function retryAsync(fn, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
return await fn();
} catch (error) {
if (i === retries - 1) throw error;
}
}
}
// Timeout wrapper
function withTimeout(promise, ms) {
return Promise.race([
promise,
new Promise((_, reject) =>
setTimeout(() => reject('Timeout'), ms)
)
]);
}
// Sequential processing
async function processSequentially(items) {
const results = [];
for (const item of items) {
const result = await processItem(item);
results.push(result);
}
return results;
}
// Parallel processing
async function processParallel(items) {
return Promise.all(
items.map(item => processItem(item))
);
}
// BEST PRACTICES
// Always return promises
async function good() {
return await fetchData(); // or just: return fetchData();
}
// Avoid unnecessary async
function unnecessary() { // Don't need async here
return fetchData(); // Already returns promise
}
// Handle errors
async function alwaysHandle() {
try {
return await fetchData();
} catch (error) {
console.error(error);
return null; // Provide fallback
}
}
// Use Promise.all for independent operations
async function efficient() {
return Promise.all([
fetchUser(),
fetchPosts(),
fetchSettings()
]);
}178. What is debouncing in JavaScript?
Difficulty: MediumType: MCQTopic: Debounce Throttle
- Removing duplicate event listeners
- Delaying function execution until after a wait period of inactivity
- Making functions run faster
- Preventing memory leaks
Debouncing delays function execution until after the user stops triggering the event for a specified time.
If the event keeps firing, the timer resets. The function only executes after the user stops for the delay period.
Debouncing is useful for search inputs, resize events, and text input validation where you want to wait until the user finishes.
It reduces the number of function calls significantly, improving performance.
Common use cases include autocomplete search, form validation, and window resize handlers.
Understanding debouncing is essential for optimizing event-heavy applications.
Correct Answer: Delaying function execution until after a wait period of inactivity
Example Code
// DEBOUNCE IMPLEMENTATION
function debounce(func, delay) {
let timeoutId;
return function(...args) {
// Clear previous timeout
clearTimeout(timeoutId);
// Set new timeout
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// HOW IT WORKS
// User types: a -> b -> c -> d
// Without debounce: 4 API calls (a, b, c, d)
// With debounce: 1 API call (after user stops typing)
// PRACTICAL EXAMPLE - Search input
const searchInput = document.getElementById('search');
function searchAPI(query) {
console.log('Searching for:', query);
// Make API call
}
// Without debounce (too many calls!)
searchInput.addEventListener('input', (e) => {
searchAPI(e.target.value); // Calls on every keystroke!
});
// With debounce (efficient)
const debouncedSearch = debounce(searchAPI, 500);
searchInput.addEventListener('input', (e) => {
debouncedSearch(e.target.value); // Only calls after 500ms pause
});
// WINDOW RESIZE EXAMPLE
function handleResize() {
console.log('Window resized to:', window.innerWidth);
// Expensive layout calculations
}
// Without debounce: hundreds of calls while resizing
window.addEventListener('resize', handleResize);
// With debounce: one call after resize stops
window.addEventListener('resize', debounce(handleResize, 250));
// FORM VALIDATION EXAMPLE
const emailInput = document.getElementById('email');
function validateEmail(email) {
console.log('Validating:', email);
// Validation logic
}
const debouncedValidate = debounce(validateEmail, 300);
emailInput.addEventListener('input', (e) => {
debouncedValidate(e.target.value);
});
// IMMEDIATE EXECUTION DEBOUNCE
function debounceImmediate(func, delay) {
let timeoutId;
return function(...args) {
const callNow = !timeoutId;
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
timeoutId = null;
}, delay);
if (callNow) {
func.apply(this, args);
}
};
}
// Executes immediately on first call,
// then waits for delay before allowing next execution
// CANCELABLE DEBOUNCE
function debounceWithCancel(func, delay) {
let timeoutId;
const debounced = function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
debounced.cancel = function() {
clearTimeout(timeoutId);
};
return debounced;
}
const debouncedFunc = debounceWithCancel(myFunction, 1000);
// Cancel if needed
debouncedFunc.cancel();
// REAL-WORLD SCENARIOS
// Autocomplete
const autocomplete = debounce(async (query) => {
const results = await fetch(`/api/search?q=${query}`);
displaySuggestions(await results.json());
}, 300);
// Scroll position saving
const saveScrollPosition = debounce(() => {
localStorage.setItem('scrollPos', window.scrollY);
}, 500);
window.addEventListener('scroll', saveScrollPosition);
// Button click protection
const handleSubmit = debounce(() => {
console.log('Form submitted');
// Submit form
}, 1000);
button.addEventListener('click', handleSubmit);179. What is the difference between debouncing and throttling?
Difficulty: MediumType: MCQTopic: Debounce Throttle
- They are the same
- Debouncing delays until inactivity, throttling limits execution frequency
- Throttling is faster than debouncing
- Debouncing only works with events
Debouncing waits for a pause in events before executing. The function runs only after the user stops triggering events.
Throttling ensures the function executes at most once per specified interval, regardless of how many times the event fires.
Debouncing is like waiting for an elevator to fill before closing doors. Throttling is like an elevator departing every 5 minutes.
Use debouncing for search, form validation, window resize. Use throttling for scroll events, mouse movement, infinite scroll.
Throttling guarantees execution frequency. Debouncing may never execute if events keep firing.
Understanding both techniques is crucial for performance optimization.
Correct Answer: Debouncing delays until inactivity, throttling limits execution frequency
Example Code
// THROTTLE IMPLEMENTATION
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => {
inThrottle = false;
}, limit);
}
};
}
// COMPARISON
// Debounce: waits for pause
// Events: |x|x|x|x|______|x|x|______
// Executes: ^ ^
// (only after pauses)
// Throttle: executes at intervals
// Events: |x|x|x|x|x|x|x|x|x|x|x|
// Executes: ^ ^ ^ ^ ^
// (every N milliseconds)
// PRACTICAL EXAMPLE - Scroll event
function handleScroll() {
console.log('Scroll position:', window.scrollY);
// Update UI based on scroll
}
// Without optimization: hundreds of calls per scroll
window.addEventListener('scroll', handleScroll);
// With throttle: max one call per 100ms
window.addEventListener('scroll', throttle(handleScroll, 100));
// With debounce: only after scrolling stops
window.addEventListener('scroll', debounce(handleScroll, 100));
// INFINITE SCROLL
const loadMore = throttle(() => {
const scrollPosition = window.scrollY + window.innerHeight;
const threshold = document.body.scrollHeight - 200;
if (scrollPosition >= threshold) {
console.log('Loading more items...');
// Load more data
}
}, 200);
window.addEventListener('scroll', loadMore);
// MOUSE MOVEMENT TRACKING
const trackMouse = throttle((e) => {
console.log('Mouse at:', e.clientX, e.clientY);
// Update cursor effect
}, 50);
document.addEventListener('mousemove', trackMouse);
// BUTTON CLICK RATE LIMITING
const handleClick = throttle(() => {
console.log('API call made');
// Make API call
}, 2000);
button.addEventListener('click', handleClick);
// Can only call once every 2 seconds
// ADVANCED THROTTLE (leading and trailing)
function throttleAdvanced(func, limit, options = {}) {
let timeout;
let previous = 0;
const { leading = true, trailing = true } = options;
return function(...args) {
const now = Date.now();
if (!previous && !leading) {
previous = now;
}
const remaining = limit - (now - previous);
if (remaining <= 0) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
func.apply(this, args);
} else if (!timeout && trailing) {
timeout = setTimeout(() => {
previous = leading ? Date.now() : 0;
timeout = null;
func.apply(this, args);
}, remaining);
}
};
}
// WHEN TO USE WHAT
// Use DEBOUNCE for:
// - Search input (wait for user to finish typing)
// - Form validation (validate after user stops)
// - Window resize (recalculate layout after resize stops)
// - Autosave (save after user stops editing)
// Use THROTTLE for:
// - Scroll events (update UI while scrolling)
// - Mouse movement (track cursor position)
// - Animation updates (limit frame rate)
// - API rate limiting (max calls per time period)
// - Infinite scroll (check position while scrolling)
// COMBINED EXAMPLE
const searchInput = document.getElementById('search');
// Debounce for search API call
const debouncedSearch = debounce((query) => {
fetch(`/api/search?q=${query}`);
}, 500);
// Throttle for character count update
const throttledCount = throttle((text) => {
document.getElementById('count').textContent = text.length;
}, 100);
searchInput.addEventListener('input', (e) => {
const value = e.target.value;
debouncedSearch(value); // API call debounced
throttledCount(value); // Count update throttled
});
// PERFORMANCE IMPACT
// Without optimization:
// 1000 events = 1000 function calls
// With throttle (100ms):
// 1000 events in 1 second = 10 function calls
// With debounce (100ms):
// 1000 events in 1 second = 1 function call (after pause)180. What is memoization in JavaScript?
Difficulty: HardType: MCQTopic: Functional JS
- Storing passwords
- Caching function results to avoid recalculation
- Memorizing code
- Creating memory leaks
Memoization caches the results of expensive function calls and returns cached result when same inputs occur again.
It trades memory for speed. Store results of previous calls and look them up instead of recalculating.
Memoization only works with pure functions. Same input must always produce same output.
Common use cases include recursive calculations like Fibonacci, complex data transformations, and API calls.
JavaScript engines do not automatically memoize. You must implement it manually.
Understanding memoization shows advanced optimization knowledge and functional programming concepts.
Correct Answer: Caching function results to avoid recalculation
Example Code
// BASIC MEMOIZATION
function memoize(fn) {
const cache = {};
return function(...args) {
const key = JSON.stringify(args);
if (key in cache) {
console.log('From cache');
return cache[key];
}
console.log('Computing...');
const result = fn(...args);
cache[key] = result;
return result;
};
}
// EXAMPLE - Expensive calculation
function slowSquare(n) {
// Simulate expensive operation
let result = 0;
for (let i = 0; i < 1000000000; i++) {
result = n * n;
}
return result;
}
const memoizedSquare = memoize(slowSquare);
console.log(memoizedSquare(5)); // Computing... 25 (takes time)
console.log(memoizedSquare(5)); // From cache 25 (instant!)
console.log(memoizedSquare(10)); // Computing... 100
console.log(memoizedSquare(5)); // From cache 25
// FIBONACCI - Classic example
// Without memoization (VERY SLOW)
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// fibonacci(40) takes several seconds!
// With memoization (FAST)
const memoizedFib = memoize(function(n) {
if (n <= 1) return n;
return memoizedFib(n - 1) + memoizedFib(n - 2);
});
// memoizedFib(40) is instant!
console.log(memoizedFib(40)); // 102334155
// ALTERNATIVE: Closure-based memoization
function fibonacciMemoized() {
const cache = {};
return function fib(n) {
if (n in cache) return cache[n];
if (n <= 1) return n;
cache[n] = fib(n - 1) + fib(n - 2);
return cache[n];
};
}
const fib = fibonacciMemoized();
console.log(fib(50)); // Fast!
// MAP-BASED MEMOIZATION
function memoizeWithMap(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn(...args);
cache.set(key, result);
return result;
};
}
// MEMOIZATION WITH WEAKMAP (for object keys)
function memoizeObjects(fn) {
const cache = new WeakMap();
return function(obj) {
if (cache.has(obj)) {
return cache.get(obj);
}
const result = fn(obj);
cache.set(obj, result);
return result;
};
}
const processObject = memoizeObjects((obj) => {
// Expensive processing
return Object.keys(obj).length;
});
// LIMITED CACHE SIZE
function memoizeWithLimit(fn, limit = 100) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn(...args);
// Remove oldest entry if limit reached
if (cache.size >= limit) {
const firstKey = cache.keys().next().value;
cache.delete(firstKey);
}
cache.set(key, result);
return result;
};
}
// PRACTICAL EXAMPLES
// API calls
const fetchUser = memoize(async (id) => {
const response = await fetch(`/api/users/${id}`);
return response.json();
});
// Same user requested multiple times: only 1 API call
await fetchUser(1); // Makes API call
await fetchUser(1); // Returns cached result
// Complex calculations
const calculateDistance = memoize((x1, y1, x2, y2) => {
return Math.sqrt(
Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)
);
});
// Data transformations
const transformData = memoize((data) => {
return data.map(item => ({
...item,
processed: expensiveOperation(item)
}));
});
// FACTORIAL WITH MEMOIZATION
const factorial = memoize(function(n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
});
console.log(factorial(100)); // Fast!
// WHEN TO USE MEMOIZATION
// Use for:
// - Pure functions (same input = same output)
// - Expensive calculations
// - Recursive algorithms
// - API calls with same parameters
// - Complex data transformations
// Don't use for:
// - Functions with side effects
// - Random or time-dependent results
// - Functions called only once
// - Very simple calculations
// - Functions with large memory footprint
// MEMOIZATION VS CACHING
// Memoization: automatic caching based on inputs
// Caching: manual storage and retrieval
// MEMORY CONSIDERATIONS
// Memoization uses memory to store results
// Clear cache periodically if needed
function memoizeWithExpiry(fn, ttl = 60000) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
const cached = cache.get(key);
if (cached && Date.now() - cached.timestamp < ttl) {
return cached.value;
}
const result = fn(...args);
cache.set(key, {
value: result,
timestamp: Date.now()
});
return result;
};
}181. What is lazy loading in JavaScript?
Difficulty: MediumType: MCQTopic: JS Performance
- Making code run slowly
- Delaying loading of resources until they are needed
- Loading all resources at once
- A debugging technique
Lazy loading defers loading of resources like images, scripts, or modules until they are actually needed.
It improves initial page load time by loading only critical resources first. Non-critical resources load on demand.
Common applications include images in viewport, code splitting, infinite scroll, and modal content.
Modern browsers support native lazy loading for images with the loading attribute.
JavaScript dynamic imports enable lazy loading of modules.
Understanding lazy loading is crucial for building performant web applications.
Correct Answer: Delaying loading of resources until they are needed
Example Code
// NATIVE IMAGE LAZY LOADING
// Modern browsers support loading="lazy"
// <img src="image.jpg" loading="lazy" alt="Description">
// INTERSECTION OBSERVER API
const images = document.querySelectorAll('img[data-src]');
const imageObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src; // Load actual image
img.removeAttribute('data-src');
imageObserver.unobserve(img);
}
});
});
images.forEach(img => imageObserver.observe(img));
// HTML:
// <img data-src="actual-image.jpg" src="placeholder.jpg" alt="">
// DYNAMIC MODULE IMPORT
// Load module only when needed
button.addEventListener('click', async () => {
const module = await import('./heavy-module.js');
module.init();
});
// Lazy load component
async function loadComponent() {
const {default: Component} = await import('./Component.js');
const component = new Component();
component.render();
}
// LAZY LOAD SCRIPTS
function loadScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
script.onload = resolve;
script.onerror = reject;
document.body.appendChild(script);
});
}
// Load only when needed
button.addEventListener('click', async () => {
await loadScript('https://cdn.example.com/library.js');
// Use library
});
// LAZY LOAD CSS
function loadCSS(href) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = href;
document.head.appendChild(link);
}
// Load styles for specific feature
if (userWantsFeature) {
loadCSS('/styles/feature.css');
}
// INFINITE SCROLL WITH LAZY LOADING
let page = 1;
let loading = false;
const loadMoreContent = async () => {
if (loading) return;
loading = true;
const data = await fetch(`/api/items?page=${page}`);
const items = await data.json();
items.forEach(item => {
container.appendChild(createItemElement(item));
});
page++;
loading = false;
};
window.addEventListener('scroll', throttle(() => {
const scrollPosition = window.scrollY + window.innerHeight;
const threshold = document.body.scrollHeight - 500;
if (scrollPosition >= threshold) {
loadMoreContent();
}
}, 200));
// LAZY LOAD MODAL CONTENT
let modalContent = null;
button.addEventListener('click', async () => {
if (!modalContent) {
// Load only on first open
modalContent = await fetch('/modal-content.html');
}
modal.innerHTML = modalContent;
modal.style.display = 'block';
});
// CODE SPLITTING WITH WEBPACK
// Webpack automatically splits this into separate chunk
import(/* webpackChunkName: "chart" */ './chart.js')
.then(module => {
const chart = new module.Chart();
chart.render();
});
// LAZY LOADING FUNCTIONS
class LazyLoader {
constructor() {
this.cache = {};
}
async load(name, loader) {
if (this.cache[name]) {
return this.cache[name];
}
const result = await loader();
this.cache[name] = result;
return result;
}
}
const loader = new LazyLoader();
// Load only when needed
const chart = await loader.load('chart', () =>
import('./chart.js')
);
// PRACTICAL PATTERNS
// Lazy load on hover (prefetch)
link.addEventListener('mouseenter', () => {
// Start loading before click
import('./page.js');
}, {once: true});
// Lazy load on idle
if ('requestIdleCallback' in window) {
requestIdleCallback(() => {
import('./analytics.js');
});
}
// Lazy load fonts
const font = new FontFace(
'MyFont',
'url(/fonts/myfont.woff2)'
);
// Load only when needed
button.addEventListener('click', async () => {
await font.load();
document.fonts.add(font);
document.body.style.fontFamily = 'MyFont';
});
// LAZY LOADING WITH WEBPACK
// Automatically code-splits
const heavyFeature = () => import('./heavy-feature');
if (condition) {
heavyFeature().then(module => {
module.init();
});
}
// BENEFITS
// - Faster initial page load
// - Reduced bandwidth usage
// - Better user experience
// - Lower server costs
// - Improved performance metrics182. Which of the following can cause memory leaks in JavaScript?
Difficulty: HardType: MCQTopic: Memory Management
- Using const instead of let
- Forgotten event listeners and timers
- Using arrow functions
- Declaring variables
Memory leaks occur when objects are no longer needed but still referenced, preventing garbage collection.
Common causes include forgotten event listeners, timers not cleared, closures holding large objects, and detached DOM nodes.
Event listeners keep references to elements and callbacks. Always remove listeners when done.
SetTimeout and setInterval must be cleared. Global variables never get garbage collected.
Closures can accidentally hold references to large objects. Be careful what you capture.
Understanding memory leaks is essential for building long-running applications.
Correct Answer: Forgotten event listeners and timers
Example Code
// MEMORY LEAK: Forgotten event listeners
function setupButton() {
const button = document.getElementById('btn');
button.addEventListener('click', function handler() {
console.log('Clicked');
});
// Memory leak! Listener never removed
// If setupButton called repeatedly, listeners accumulate
}
// FIX: Remove listener
function setupButtonCorrect() {
const button = document.getElementById('btn');
function handler() {
console.log('Clicked');
}
button.addEventListener('click', handler);
// Clean up
return () => {
button.removeEventListener('click', handler);
};
}
const cleanup = setupButtonCorrect();
// Later: cleanup();
// MEMORY LEAK: Timers not cleared
function startTimer() {
setInterval(() => {
console.log('Running');
}, 1000);
// Memory leak! Timer never stopped
}
// FIX: Clear timer
function startTimerCorrect() {
const timerId = setInterval(() => {
console.log('Running');
}, 1000);
return () => {
clearInterval(timerId);
};
}
const stopTimer = startTimerCorrect();
// Later: stopTimer();
// MEMORY LEAK: Closures holding large data
function createClosure() {
const hugeArray = new Array(1000000).fill('data');
return function() {
// Only uses one element
console.log(hugeArray[0]);
// But holds reference to entire array!
};
}
// FIX: Only capture what you need
function createClosureCorrect() {
const hugeArray = new Array(1000000).fill('data');
const firstElement = hugeArray[0]; // Extract needed data
return function() {
console.log(firstElement);
// Only holds one element, not entire array
};
}
// MEMORY LEAK: Detached DOM nodes
const elements = [];
function createElements() {
const div = document.createElement('div');
document.body.appendChild(div);
elements.push(div); // Store reference
// Remove from DOM
document.body.removeChild(div);
// Memory leak! Still referenced in array
}
// FIX: Remove references
function createElementsCorrect() {
const div = document.createElement('div');
document.body.appendChild(div);
return () => {
document.body.removeChild(div);
// No stored reference, can be garbage collected
};
}
// MEMORY LEAK: Global variables
function processData() {
// Accidental global (no var/let/const)
data = new Array(1000000);
// Never garbage collected!
}
// FIX: Use proper declarations
function processDataCorrect() {
const data = new Array(1000000);
// Garbage collected when function ends
}
// MEMORY LEAK: Circular references (old IE)
function createCircular() {
const obj1 = {};
const obj2 = {};
obj1.ref = obj2;
obj2.ref = obj1;
// Modern engines handle this
// But be aware in older browsers
}
// DETECTING MEMORY LEAKS
// Use Chrome DevTools Memory Profiler:
// 1. Take heap snapshot
// 2. Perform action
// 3. Take another snapshot
// 4. Compare snapshots
// 5. Look for growing objects
// Monitor memory programmatically
if (performance.memory) {
console.log('Used:', performance.memory.usedJSHeapSize);
console.log('Total:', performance.memory.totalJSHeapSize);
}
// BEST PRACTICES TO AVOID LEAKS
// 1. Always remove event listeners
class Component {
constructor() {
this.handleClick = this.handleClick.bind(this);
}
mount() {
button.addEventListener('click', this.handleClick);
}
unmount() {
button.removeEventListener('click', this.handleClick);
}
handleClick() {
console.log('Clicked');
}
}
// 2. Clear all timers
class Timer {
constructor() {
this.timers = [];
}
setTimeout(callback, delay) {
const id = setTimeout(callback, delay);
this.timers.push(id);
}
cleanup() {
this.timers.forEach(id => clearTimeout(id));
this.timers = [];
}
}
// 3. Use WeakMap/WeakSet for object keys
const cache = new WeakMap();
function processObject(obj) {
if (cache.has(obj)) {
return cache.get(obj);
}
const result = expensiveOperation(obj);
cache.set(obj, result);
return result;
}
// When obj is garbage collected, entry removed automatically
// 4. Avoid global variables
// Use modules, IIFEs, or proper scoping
// 5. Be careful with closures
function createHandlers() {
const data = fetchLargeData();
// Bad: captures entire data object
const handler1 = () => console.log(data.field1);
// Good: only captures needed field
const field1 = data.field1;
const handler2 = () => console.log(field1);
return handler2;
}183. What are Web Workers used for in JavaScript?
Difficulty: HardType: MCQTopic: Web Workers
- Creating web servers
- Running JavaScript in background threads
- Managing databases
- Styling web pages
Web Workers allow running JavaScript in background threads separate from the main UI thread.
They enable true parallelism for CPU-intensive tasks without blocking the UI.
Workers cannot access the DOM. They communicate with main thread via message passing.
Use workers for heavy computations, data processing, image manipulation, or encryption.
Workers have their own global scope, no access to window or document.
Understanding workers shows advanced knowledge of JavaScript performance optimization.
Correct Answer: Running JavaScript in background threads
Example Code
// CREATING A WEB WORKER
// worker.js
self.addEventListener('message', (e) => {
const {data} = e;
// Heavy computation
const result = expensiveCalculation(data);
// Send result back
self.postMessage(result);
});
function expensiveCalculation(n) {
let sum = 0;
for (let i = 0; i < n; i++) {
sum += Math.sqrt(i);
}
return sum;
}
// USING THE WORKER (main thread)
const worker = new Worker('worker.js');
// Send data to worker
worker.postMessage(1000000);
// Receive result from worker
worker.addEventListener('message', (e) => {
console.log('Result:', e.data);
});
// Handle errors
worker.addEventListener('error', (e) => {
console.error('Worker error:', e.message);
});
// Terminate worker when done
worker.terminate();
// PRACTICAL EXAMPLE - Image processing
// imageWorker.js
self.addEventListener('message', (e) => {
const {imageData} = e.data;
// Process each pixel
for (let i = 0; i < imageData.data.length; i += 4) {
// Convert to grayscale
const avg = (
imageData.data[i] +
imageData.data[i + 1] +
imageData.data[i + 2]
) / 3;
imageData.data[i] = avg; // R
imageData.data[i + 1] = avg; // G
imageData.data[i + 2] = avg; // B
}
self.postMessage({imageData});
});
// Main thread
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const imageWorker = new Worker('imageWorker.js');
imageWorker.postMessage({imageData});
imageWorker.addEventListener('message', (e) => {
ctx.putImageData(e.data.imageData, 0, 0);
});
// DATA PROCESSING EXAMPLE
// dataWorker.js
self.addEventListener('message', (e) => {
const {data} = e;
// Process large dataset
const processed = data.map(item => ({
...item,
calculated: complexCalculation(item)
}));
self.postMessage(processed);
});
// Main thread
const dataWorker = new Worker('dataWorker.js');
fetch('/api/data')
.then(r => r.json())
.then(data => {
// Offload processing to worker
dataWorker.postMessage({data});
});
dataWorker.addEventListener('message', (e) => {
displayData(e.data);
});
// SHARED WORKERS (shared across tabs)
// sharedWorker.js
let connections = [];
self.addEventListener('connect', (e) => {
const port = e.ports[0];
connections.push(port);
port.addEventListener('message', (e) => {
// Broadcast to all connections
connections.forEach(conn => {
conn.postMessage(e.data);
});
});
port.start();
});
// Using shared worker
const sharedWorker = new SharedWorker('sharedWorker.js');
sharedWorker.port.addEventListener('message', (e) => {
console.log('Received:', e.data);
});
sharedWorker.port.start();
sharedWorker.port.postMessage('Hello');
// LIMITATIONS OF WEB WORKERS
// Cannot access:
// - DOM (document, window)
// - Parent page variables
// - Some browser APIs
// Can access:
// - navigator
// - location (read-only)
// - XMLHttpRequest
// - setTimeout/setInterval
// - importScripts()
// IMPORTING SCRIPTS IN WORKER
// worker.js
importScripts('library1.js', 'library2.js');
// Now can use functions from imported scripts
// TRANSFERABLE OBJECTS (avoid copying)
// Instead of copying large data, transfer ownership
const buffer = new ArrayBuffer(1024 * 1024); // 1MB
// Transfer (zero-copy)
worker.postMessage({buffer}, [buffer]);
// buffer is now unusable in main thread
// In worker
self.addEventListener('message', (e) => {
const {buffer} = e.data;
// Now worker owns the buffer
});
// WORKER POOL PATTERN
class WorkerPool {
constructor(workerScript, poolSize = 4) {
this.workers = [];
this.queue = [];
for (let i = 0; i < poolSize; i++) {
const worker = new Worker(workerScript);
worker.busy = false;
worker.addEventListener('message', (e) => {
worker.busy = false;
this.processQueue();
});
this.workers.push(worker);
}
}
execute(data) {
return new Promise((resolve, reject) => {
this.queue.push({data, resolve, reject});
this.processQueue();
});
}
processQueue() {
if (this.queue.length === 0) return;
const worker = this.workers.find(w => !w.busy);
if (!worker) return;
const task = this.queue.shift();
worker.busy = true;
worker.postMessage(task.data);
worker.onmessage = (e) => task.resolve(e.data);
worker.onerror = (e) => task.reject(e);
}
}
const pool = new WorkerPool('worker.js', 4);
// Use pool
pool.execute({data: 100}).then(result => {
console.log(result);
});184. Why use requestAnimationFrame instead of setInterval for animations?
Difficulty: MediumType: MCQTopic: JS Performance
- It is slower
- It syncs with browser refresh rate and pauses when tab inactive
- It is easier to write
- It works in older browsers
RequestAnimationFrame synchronizes with the browser's refresh rate, typically 60 FPS.
It automatically pauses when the tab is inactive, saving CPU and battery.
SetInterval runs at fixed intervals regardless of browser rendering, causing jank.
RAF ensures animations run smoothly by timing updates with screen repaints.
The browser can optimize RAF calls, batching them together.
Understanding RAF is essential for creating smooth, performant animations.
Correct Answer: It syncs with browser refresh rate and pauses when tab inactive
Example Code
// BAD: Using setInterval
let position = 0;
setInterval(() => {
position += 2;
element.style.left = position + 'px';
}, 16); // ~60 FPS
// Problems:
// - Not synced with screen refresh
// - Runs even when tab inactive
// - Can cause frame drops
// GOOD: Using requestAnimationFrame
let position2 = 0;
function animate() {
position2 += 2;
element.style.left = position2 + 'px';
if (position2 < 500) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
// Benefits:
// - Synced with refresh rate
// - Pauses when tab inactive
// - Smooth animations
// ANIMATION WITH TIMESTAMP
function animateWithTime(timestamp) {
// timestamp is high-resolution time
const progress = timestamp / 1000; // Convert to seconds
element.style.left = (progress * 100) % 500 + 'px';
requestAnimationFrame(animateWithTime);
}
requestAnimationFrame(animateWithTime);
// SMOOTH MOVEMENT
let lastTime = 0;
let x = 0;
const speed = 100; // pixels per second
function smoothMove(currentTime) {
// Calculate delta time
const deltaTime = (currentTime - lastTime) / 1000;
lastTime = currentTime;
// Update position based on time
x += speed * deltaTime;
element.style.left = x + 'px';
if (x < 500) {
requestAnimationFrame(smoothMove);
}
}
requestAnimationFrame(smoothMove);
// CANCELING ANIMATION
let animationId;
function animate() {
// Animation code
animationId = requestAnimationFrame(animate);
}
animationId = requestAnimationFrame(animate);
// Stop animation
cancelAnimationFrame(animationId);
// GAME LOOP PATTERN
class Game {
constructor() {
this.running = false;
this.lastTime = 0;
}
start() {
this.running = true;
requestAnimationFrame((time) => this.gameLoop(time));
}
stop() {
this.running = false;
}
gameLoop(currentTime) {
if (!this.running) return;
const deltaTime = currentTime - this.lastTime;
this.lastTime = currentTime;
// Update game state
this.update(deltaTime);
// Render
this.render();
// Continue loop
requestAnimationFrame((time) => this.gameLoop(time));
}
update(deltaTime) {
// Update game objects
}
render() {
// Draw to canvas
}
}
const game = new Game();
game.start();
// SCROLLING PARALLAX
let ticking = false;
window.addEventListener('scroll', () => {
if (!ticking) {
requestAnimationFrame(() => {
updateParallax();
ticking = false;
});
ticking = true;
}
});
function updateParallax() {
const scrollY = window.scrollY;
background.style.transform = `translateY(${scrollY * 0.5}px)`;
}
// SMOOTH COUNTER
function animateValue(start, end, duration) {
const startTime = performance.now();
function update(currentTime) {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
// Easing function
const easeProgress = progress * (2 - progress);
const current = start + (end - start) * easeProgress;
element.textContent = Math.floor(current);
if (progress < 1) {
requestAnimationFrame(update);
}
}
requestAnimationFrame(update);
}
animateValue(0, 1000, 2000); // Count from 0 to 1000 in 2 seconds
// FPS COUNTER
let fps = 0;
let frameCount = 0;
let lastFpsUpdate = 0;
function countFPS(currentTime) {
frameCount++;
if (currentTime - lastFpsUpdate >= 1000) {
fps = frameCount;
frameCount = 0;
lastFpsUpdate = currentTime;
console.log('FPS:', fps);
}
requestAnimationFrame(countFPS);
}
requestAnimationFrame(countFPS);
// BATCH DOM UPDATES
const updates = [];
let scheduled = false;
function scheduleUpdate(callback) {
updates.push(callback);
if (!scheduled) {
scheduled = true;
requestAnimationFrame(processBatch);
}
}
function processBatch() {
const batch = updates.slice();
updates.length = 0;
scheduled = false;
batch.forEach(callback => callback());
}
// Usage
scheduleUpdate(() => {
element1.style.left = '100px';
});
scheduleUpdate(() => {
element2.style.top = '200px';
});
// Both updates happen in same frame!185. What is code splitting in JavaScript?
Difficulty: MediumType: MCQTopic: JS Performance
- Dividing code into multiple files
- Breaking application into smaller bundles loaded on demand
- Writing code in multiple languages
- Separating HTML, CSS, and JavaScript
Code splitting breaks your application into smaller chunks that can be loaded on demand.
It reduces the initial bundle size, improving load time. Only load code when needed.
Modern bundlers like Webpack automatically create split points with dynamic imports.
Route-based splitting loads code for each route separately.
Component-based splitting loads heavy components only when rendered.
Understanding code splitting is crucial for optimizing large JavaScript applications.
Correct Answer: Breaking application into smaller bundles loaded on demand
Example Code
// DYNAMIC IMPORT (ES2020)
// Load module on demand
button.addEventListener('click', async () => {
const module = await import('./heavy-feature.js');
module.init();
});
// Webpack creates separate chunk automatically
// ROUTE-BASED CODE SPLITTING
// Router configuration
const routes = [
{
path: '/home',
component: () => import('./views/Home.js')
},
{
path: '/about',
component: () => import('./views/About.js')
},
{
path: '/dashboard',
component: () => import('./views/Dashboard.js')
}
];
// Load route component on navigation
async function navigateTo(path) {
const route = routes.find(r => r.path === path);
const module = await route.component();
const component = new module.default();
component.render();
}
// COMPONENT-BASED SPLITTING
// Lazy load heavy component
class App {
async loadChart() {
if (!this.ChartComponent) {
const module = await import('./Chart.js');
this.ChartComponent = module.default;
}
return new this.ChartComponent();
}
async showChart() {
const chart = await this.loadChart();
chart.render();
}
}
// REACT CODE SPLITTING
import React, {lazy, Suspense} from 'react';
// Lazy load component
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
);
}
// WEBPACK MAGIC COMMENTS
// Name the chunk
import(/* webpackChunkName: "chart" */ './chart.js');
// Prefetch (load when idle)
import(/* webpackPrefetch: true */ './future-page.js');
// Preload (load in parallel)
import(/* webpackPreload: true */ './critical.js');
// VENDOR SPLITTING
// webpack.config.js
module.exports = {
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
};
// CONDITIONAL LOADING
let component;
if (userPreference === 'advanced') {
component = await import('./AdvancedView.js');
} else {
component = await import('./SimpleView.js');
}
component.render();
// FEATURE FLAGS WITH CODE SPLITTING
const features = await fetch('/api/features').then(r => r.json());
if (features.betaFeature) {
const betaModule = await import('./beta-feature.js');
betaModule.enable();
}
// LIBRARY SPLITTING
// Load only when needed
async function processCSV(file) {
const Papa = await import('papaparse');
return Papa.parse(file);
}
async function renderChart(data) {
const Chart = await import('chart.js');
return new Chart(data);
}
// PROGRESSIVE ENHANCEMENT
// Load basic version first
import './basic.js';
// Load enhanced version when idle
if ('requestIdleCallback' in window) {
requestIdleCallback(async () => {
await import('./enhanced.js');
});
}
// MEASURING IMPACT
// Before code splitting:
// bundle.js: 500KB (loads immediately)
// After code splitting:
// main.js: 50KB (loads immediately)
// home.js: 30KB (loads on /home)
// dashboard.js: 100KB (loads on /dashboard)
// chart.js: 200KB (loads when chart needed)
// Total still 380KB, but initial load only 50KB!
// LAZY LOADING WITH CACHE
const moduleCache = new Map();
async function loadModule(path) {
if (moduleCache.has(path)) {
return moduleCache.get(path);
}
const module = await import(path);
moduleCache.set(path, module);
return module;
}
// BENEFITS
// - Faster initial load
// - Smaller initial bundle
// - Better caching
// - Improved performance metrics
// - Better user experience186. Explain debouncing, throttling, and memoization. When would you use each? Provide implementation and real-world examples.
Difficulty: HardType: SubjectiveTopic: JS Performance
Debouncing, throttling, and memoization are three critical optimization techniques that solve different performance problems.
Debouncing:
Delays function execution until after events stop firing. Resets timer on each event. Function only executes after specified quiet period. Perfect for search inputs, form validation, window resize. Reduces API calls dramatically. Implementation uses setTimeout and clearTimeout.
Throttling:
Limits function execution to once per time interval. Guarantees function runs at regular intervals. Does not wait for quiet period like debounce. Perfect for scroll events, mouse movement, animations, infinite scroll. Ensures consistent execution rate. Implementation uses timestamp or flag checking.
Memoization:
Caches function results based on inputs. Returns cached value for repeated calls with same arguments. Only works with pure functions. Perfect for expensive calculations, recursive algorithms, API calls. Trades memory for speed. Implementation uses Map or object as cache.
Key differences:
Debounce waits for pause in events. Throttle limits frequency during continuous events. Memoization caches results, not execution timing. Debounce may never execute if events keep firing. Throttle guarantees execution. Memoization guarantees same output for same input.
When to use debouncing:
Search autocomplete, wait for user to stop typing. Form field validation after user finishes. Window resize handlers. Autosave after editing stops. Any scenario where you want to wait for user to finish action.
When to use throttling:
Scroll event handlers updating UI. Mouse position tracking. Animation frame limiting. Infinite scroll loading. Button click rate limiting. Any scenario where you need regular updates during continuous events.
When to use memoization:
Expensive calculations called repeatedly. Recursive algorithms like Fibonacci. Complex data transformations. API calls with same parameters. Pure function results. Any scenario where calculation is expensive and repeated.
Implementation considerations:
Debounce and throttle need cleanup. Memoization needs memory management. Consider cache size limits. Use WeakMap for object keys when appropriate. Clear caches periodically if needed.
Real-world performance impact:
Debouncing search can reduce API calls from hundreds to one. Throttling scroll can reduce handler calls from thousands to tens. Memoization can turn O(2^n) Fibonacci into O(n). Proper use can make applications 10-100x more performant.
All three techniques are fundamental to building performant web applications and frequently appear in technical interviews.
Example Code
// DEBOUNCE IMPLEMENTATION
function debounce(func, delay) {
let timeoutId;
return function debounced(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// Debounce with immediate execution option
function debounceAdvanced(func, delay, immediate = false) {
let timeoutId;
return function(...args) {
const callNow = immediate && !timeoutId;
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
timeoutId = null;
if (!immediate) {
func.apply(this, args);
}
}, delay);
if (callNow) {
func.apply(this, args);
}
};
}
// THROTTLE IMPLEMENTATION
function throttle(func, limit) {
let inThrottle;
return function throttled(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => {
inThrottle = false;
}, limit);
}
};
}
// Throttle with leading and trailing options
function throttleAdvanced(func, limit, options = {}) {
let timeout;
let previous = 0;
const {leading = true, trailing = true} = options;
return function(...args) {
const now = Date.now();
if (!previous && !leading) {
previous = now;
}
const remaining = limit - (now - previous);
if (remaining <= 0) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
func.apply(this, args);
} else if (!timeout && trailing) {
timeout = setTimeout(() => {
previous = leading ? Date.now() : 0;
timeout = null;
func.apply(this, args);
}, remaining);
}
};
}
// MEMOIZATION IMPLEMENTATION
function memoize(fn) {
const cache = new Map();
return function memoized(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
// Memoization with cache limit
function memoizeWithLimit(fn, limit = 100) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn.apply(this, args);
if (cache.size >= limit) {
const firstKey = cache.keys().next().value;
cache.delete(firstKey);
}
cache.set(key, result);
return result;
};
}
// REAL-WORLD EXAMPLES
// 1. DEBOUNCE - Search autocomplete
const searchInput = document.getElementById('search');
const searchAPI = async (query) => {
const response = await fetch(`/api/search?q=${query}`);
const results = await response.json();
displayResults(results);
};
const debouncedSearch = debounce(searchAPI, 300);
searchInput.addEventListener('input', (e) => {
debouncedSearch(e.target.value);
});
// User types: a-b-c-d-e
// Without debounce: 5 API calls
// With debounce: 1 API call (after user stops)
// 2. THROTTLE - Infinite scroll
const loadMore = throttle(async () => {
const scrollPos = window.scrollY + window.innerHeight;
const threshold = document.body.scrollHeight - 500;
if (scrollPos >= threshold) {
const data = await fetch('/api/more');
appendItems(await data.json());
}
}, 200);
window.addEventListener('scroll', loadMore);
// User scrolls continuously
// Without throttle: hundreds of calls
// With throttle: max 5 calls per second
// 3. MEMOIZATION - Fibonacci
const fibonacci = memoize(function(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
});
console.log(fibonacci(40)); // Fast!
// Without memoization: billions of calculations
// With memoization: 40 calculations
// COMBINED USAGE
class SearchComponent {
constructor() {
// Debounce API calls
this.search = debounce(this.searchAPI.bind(this), 300);
// Throttle UI updates
this.updateUI = throttle(this.render.bind(this), 100);
// Memoize expensive calculations
this.processResults = memoize(this.process.bind(this));
}
async searchAPI(query) {
const results = await fetch(`/api/search?q=${query}`);
return results.json();
}
process(data) {
// Expensive data transformation
return data.map(item => complexCalculation(item));
}
render(data) {
// Update DOM
}
}
// PERFORMANCE COMPARISON
// Example: 1000 scroll events in 1 second
// No optimization:
// 1000 function calls
// 1000 DOM updates
// Very slow, janky scrolling
// With throttle (100ms):
// 10 function calls
// 10 DOM updates
// Smooth scrolling
// Example: User types "javascript" (10 characters)
// No optimization:
// 10 API calls
// Expensive, server load
// With debounce (300ms):
// 1 API call
// Efficient, better UX
// Example: Recursive Fibonacci(40)
// No memoization:
// 2,692,537,104 function calls
// Takes several seconds
// With memoization:
// 40 function calls
// Instant result187. Explain memory leaks in JavaScript. What causes them and how do you prevent them? Provide examples.
Difficulty: HardType: SubjectiveTopic: Memory Management
Memory leaks occur when objects that are no longer needed remain in memory because they are still referenced, preventing garbage collection.
How JavaScript garbage collection works:
Mark and sweep algorithm identifies reachable objects. Objects without references are collected. Circular references are handled automatically in modern engines. Understanding this helps prevent leaks.
Common causes of memory leaks:
Forgotten event listeners that hold references to elements and callbacks. Timers not cleared with clearTimeout or clearInterval. Closures capturing large objects unnecessarily. Detached DOM nodes still referenced in JavaScript. Global variables never garbage collected. Circular references in old browsers.
Event listener leaks:
Adding listeners without removing creates persistent references. Each listener holds reference to callback and element. Multiple calls add multiple listeners. Always remove listeners on cleanup. Use weak references when possible.
Timer leaks:
SetTimeout and setInterval keep callbacks in memory. Intervals run forever unless cleared. Callbacks may reference large objects. Always clear timers when done. Store timer IDs for cleanup.
Closure leaks:
Closures capture entire scope chain. May hold references to large objects unnecessarily. Extract only needed data before creating closure. Be mindful of what closures capture.
DOM node leaks:
Removing node from DOM does not remove JavaScript references. Storing nodes in arrays or objects prevents collection. Event listeners keep nodes alive. Clear all references when removing nodes.
Detecting memory leaks:
Use Chrome DevTools Memory Profiler. Take heap snapshots before and after actions. Compare snapshots to find growing objects. Look for detached DOM nodes. Monitor memory usage over time. Use performance.memory API.
Preventing memory leaks:
Always remove event listeners on cleanup. Clear all timers. Use WeakMap and WeakSet for object keys. Avoid global variables. Be careful with closures. Remove DOM references. Use proper lifecycle management in frameworks.
Best practices:
Implement cleanup methods in classes. Use weak references when appropriate. Clear caches periodically. Test for leaks during development. Monitor production memory usage. Use tools to detect leaks early.
Framework considerations:
React: cleanup in useEffect return or componentWillUnmount. Vue: cleanup in beforeDestroy or onUnmounted. Angular: cleanup in ngOnDestroy. All frameworks need manual cleanup of external resources.
Understanding and preventing memory leaks is crucial for building stable, long-running JavaScript applications.
Example Code
// MEMORY LEAK EXAMPLES AND FIXES
// 1. EVENT LISTENER LEAK
// BAD: Listener never removed
function setupButton() {
const button = document.getElementById('btn');
button.addEventListener('click', handleClick);
// If setupButton called multiple times, listeners accumulate!
}
// GOOD: Remove listener
function setupButtonCorrect() {
const button = document.getElementById('btn');
function handleClick() {
console.log('Clicked');
}
button.addEventListener('click', handleClick);
// Return cleanup function
return () => {
button.removeEventListener('click', handleClick);
};
}
const cleanup = setupButtonCorrect();
// When done: cleanup();
// 2. TIMER LEAK
// BAD: Timer never cleared
function startUpdates() {
setInterval(() => {
updateData();
}, 1000);
// Runs forever, holds references!
}
// GOOD: Store and clear timer
class DataUpdater {
start() {
this.intervalId = setInterval(() => {
this.updateData();
}, 1000);
}
stop() {
clearInterval(this.intervalId);
}
updateData() {
// Update logic
}
}
const updater = new DataUpdater();
updater.start();
// When done: updater.stop();
// 3. CLOSURE LEAK
// BAD: Closure captures huge object
function createHandler() {
const hugeData = new Array(1000000).fill('data');
return function() {
console.log(hugeData[0]);
// Holds entire array in memory!
};
}
// GOOD: Extract only what you need
function createHandlerCorrect() {
const hugeData = new Array(1000000).fill('data');
const firstItem = hugeData[0]; // Extract needed data
return function() {
console.log(firstItem);
// Only holds one item
};
}
// 4. DETACHED DOM NODE LEAK
// BAD: Node removed from DOM but still referenced
const nodes = [];
function addNode() {
const div = document.createElement('div');
document.body.appendChild(div);
nodes.push(div); // Store reference
// Later remove from DOM
div.remove();
// Still in array, can't be garbage collected!
}
// GOOD: Clear references
function addNodeCorrect() {
const div = document.createElement('div');
document.body.appendChild(div);
return () => {
div.remove();
// No stored reference, can be garbage collected
};
}
// 5. GLOBAL VARIABLE LEAK
// BAD: Accidental global
function processData() {
data = fetchData(); // No var/let/const = global!
// Never garbage collected
}
// GOOD: Proper scoping
function processDataCorrect() {
const data = fetchData();
// Garbage collected when function ends
}
// PROPER CLEANUP PATTERNS
// Class with cleanup
class Component {
constructor() {
this.listeners = [];
this.timers = [];
}
mount() {
// Add event listeners
const handler = () => this.handleClick();
button.addEventListener('click', handler);
this.listeners.push({element: button, event: 'click', handler});
// Start timers
const timerId = setInterval(() => this.update(), 1000);
this.timers.push(timerId);
}
unmount() {
// Remove all listeners
this.listeners.forEach(({element, event, handler}) => {
element.removeEventListener(event, handler);
});
this.listeners = [];
// Clear all timers
this.timers.forEach(id => clearInterval(id));
this.timers = [];
}
handleClick() {
console.log('Clicked');
}
update() {
console.log('Updating');
}
}
// USING WEAKMAP TO PREVENT LEAKS
// BAD: Regular Map holds references
const cache = new Map();
function process(obj) {
if (cache.has(obj)) {
return cache.get(obj);
}
const result = expensiveOperation(obj);
cache.set(obj, result);
return result;
}
// Objects stay in cache forever!
// GOOD: WeakMap allows garbage collection
const weakCache = new WeakMap();
function processWithWeakMap(obj) {
if (weakCache.has(obj)) {
return weakCache.get(obj);
}
const result = expensiveOperation(obj);
weakCache.set(obj, result);
return result;
}
// When obj is garbage collected, entry removed automatically
// DETECTING MEMORY LEAKS
// Monitor memory usage
if (performance.memory) {
setInterval(() => {
console.log('Memory used:',
Math.round(performance.memory.usedJSHeapSize / 1048576) + ' MB'
);
}, 5000);
}
// Chrome DevTools:
// 1. Open DevTools > Memory tab
// 2. Take heap snapshot
// 3. Perform action (e.g., open/close modal)
// 4. Take another snapshot
// 5. Compare snapshots
// 6. Look for objects that should be gone but aren't
// FRAMEWORK CLEANUP EXAMPLES
// React
function MyComponent() {
useEffect(() => {
const handleResize = () => console.log('Resize');
window.addEventListener('resize', handleResize);
const timer = setInterval(() => console.log('Tick'), 1000);
// Cleanup
return () => {
window.removeEventListener('resize', handleResize);
clearInterval(timer);
};
}, []);
return <div>Component</div>;
}
// Vue
export default {
mounted() {
this.timer = setInterval(() => console.log('Tick'), 1000);
window.addEventListener('resize', this.handleResize);
},
beforeUnmount() {
clearInterval(this.timer);
window.removeEventListener('resize', this.handleResize);
},
methods: {
handleResize() {
console.log('Resize');
}
}
};
// TESTING FOR LEAKS
// Simple leak test
function testForLeaks() {
const initial = performance.memory.usedJSHeapSize;
// Perform action 1000 times
for (let i = 0; i < 1000; i++) {
createComponent();
destroyComponent();
}
// Force garbage collection (in Chrome with --expose-gc flag)
if (global.gc) global.gc();
const final = performance.memory.usedJSHeapSize;
const leaked = final - initial;
console.log(`Memory leaked: ${leaked / 1024} KB`);
}188. What is the temporal dead zone (TDZ)?
Difficulty: EASYType: SUBJECTIVETopic: Scope Hoisting
In JavaScript, the Temporal Dead Zone (TDZ) is:
The time between entering a scope and the actual line where a let or const variable is declared, during which you cannot access that variable.
Even though let and const are hoisted, they are not initialized until their declaration is executed.
If you try to use them before that point, you get a ReferenceError, not undefined.
Example Code
console.log(x); // ❌ ReferenceError: Cannot access 'x' before initialization
let x = 10;
189. What is strict mode "use strict" and what does it change?
Difficulty: EASYType: SUBJECTIVETopic: JS Basics
Strict mode, enabled by the directive "use strict";, is a feature in JavaScript introduced in ECMAScript 5 that enforces stricter parsing and error handling on code at runtime. It helps developers write cleaner, safer, and more secure code by catching common mistakes and enforcing a more robust set of rules.
trict mode makes several changes to normal JavaScript semantics:
Eliminates some JavaScript silent errors by changing them to throw errors.
Fixes mistakes that make it difficult for JavaScript engines to perform optimizations: strict mode code can sometimes be made to run faster than identical code that's not strict mode.
Prohibits some syntax likely to be defined in future versions of ECMAScript.
190. What is a closure?
Difficulty: MEDIUMType: CONCEPTUALTopic: JS Functions
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).
191. What is a closure?
Difficulty: MEDIUMType: CONCEPTUALTopic: JS Functions
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).
192. What is a closure?
Difficulty: MEDIUMType: CONCEPTUALTopic: JS Functions
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).