learning-zone/javascript-basics

JavaScript Basics

HTML

1,870

1,305 commits

updated May 27, 2026

See the code

README

JavaScript Basics

Click ★ if you like the project. Your contributions are heartily ♡ welcome.



Table of Contents

L1: Fundamental (Entry-Level / Junior)

Focus: Syntax, basic logic, and standard data handling.

  • Introduction: ES6 new features
  • Variables: var, let, const.
  • Data Types: Primitives vs. Objects.
  • Operators: Arithmetic, comparison (== vs ===), and logical.
  • Numbers: isNaN, isFinite, NEGATIVE_INFINITY
  • String: slice, splice, trim, eval, replace
  • Arrays: Basic manipulation using push(), pop(), shift, indexOf etc.
  • Control Flow: Simple if/else and for loops.
  • Functions: Standard declarations and parameters.

L2: Intermediate (Junior-Mid / Developer)

Focus: ES6+ features, DOM, and common "trick" concepts.

L3: Advanced (Mid-Senior / Lead)

Focus: Asynchronous operations, performance, and internal engine mechanics. [4]

L4: Expert (Senior / Architect)

Focus: Scalability, security, and low-level optimization.

L5: Technical Lead

Focus: Code review, team standards, architectural decisions, and engineering best practices for leading a development team.

L6: Technical Architect

Focus: System-level design, scalability, security posture, micro-frontend, and cross-team JavaScript architecture.


L1: Fundamental (Entry-Level / Junior)


# 1. Introduction


Q. List out important features of JavaScript ES6?

1. Template Strings:

Template literals are string literals allowing embedded expressions.

Benefits:

  • String interpolation
  • Embedded expressions
  • Multiline strings without hacks
  • String formatting
  • String tagging for safe HTML escaping, localization and more
// String Substitution
let name = `Abhinav Sharma`;
console.log(`Hi, ${name}`); // Output: "Hi, Abhinav Sharma"

// Multiline String
let msg = `Hello \n
World`;
console.log(`${msg}`); // Output: "Hello World"

⚝ Try this example on CodeSandbox

2. Spread Operator:

Spread operator allows iterables( arrays / objects / strings ) to be expanded into single arguments/elements.

function sum(x, y, z) {
  return x + y + z;
}
const numbers = [10, 20, 30];

// Using Apply (ES5)
console.log(sum.apply(null, numbers)); // 60

// Using Spread Operator
console.log(sum(...numbers)); // 60

⚝ Try this example on CodeSandbox

2.1. Copying an array:

const fruits = ["Apple", "Orange", "Banana"];
const newFruitArray = [...fruits];

console.log(newFruitArray); 

Output:

['Apple', 'Orange', 'Banana']

⚝ Try this example on CodeSandbox

2.2. Concatenating arrays:

const arr1 = ["A", "B", "C"];
const arr2 = ["X", "Y", "Z"];

const result = [...arr1, ...arr2];

console.log(result); 

Output:

['A', 'B', 'C', 'X', 'Y', 'Z']

⚝ Try this example on CodeSandbox

2.3. Spreading elements together with an individual element:

const fruits = ["Apple", "Orange", "Banana"];
const newFruits = ["Cherry", ...fruits];

console.log(newFruits); 

Output:

['Cherry', 'Apple', 'Orange', 'Banana']

⚝ Try this example on CodeSandbox

2.4. Spreading elements on function calls:

const fruits = ["Apple", "Orange", "Banana"];

const getFruits = (f1, f2, f3) => {
  console.log(`Fruits: ${f1}, ${f2} and ${f3}`);
};

getFruits(...fruits); 

Output:

Fruits: Apple, Orange and Banana

⚝ Try this example on CodeSandbox

2.5. Spread syntax for object literals:

const obj1 = { id: 101, name: 'Rajiv Sandal' }
const obj2 = { age: 35, country: 'INDIA' }

const employee = { ...obj1, ...obj2 }

console.log(employee);

Output:

{
  "id": 101,
  "name": "Rajiv Sandal",
  "age": 35,
  "country": "INDIA"
}

3. Sets:

Sets are a new object type with ES6 (ES2015) that allow to create collections of unique values. The values in a set can be either simple primitives like strings or integers, but more complex object types like object literals or arrays can also be part of a set.

const numbers = new Set([10, 20, 20, 30, 40, 50]);

console.log(numbers); Set(5) { 10, 20, 30, 40, 50 }
console.log(typeof numbers); // Object

⚝ Try this example on CodeSandbox

4. Default Parametrs:

function add(x = 10, y = 20) {
  console.log(x + y);
}

add(10, 30); // Output: 10 + 30 = 40
add(15); // Output: 15 + 20 = 35
add(); // Output: 10 + 20 = 30

⚝ Try this example on CodeSandbox

5. repeat():

The repeat() method constructs and returns a new string which contains the specified number of copies of the string on which it was called, concatenated together.

const msg = "Hello World \n";

console.log(`${msg.repeat(3)}`);

Output:

Hello World
Hello World
Hello World

⚝ Try this example on CodeSandbox

6. Arrow Function (=>):

const add = (x, y) => x + y;

console.log(add(10, 20)); // 30

⚝ Try this example on CodeSandbox

7. Arrow function with this

/**
 * Using Arrow function
 */
const person = {
  name: "Diksha",
  actions: ["bike", "hike", "ski"],
  printActions() {
    this.actions.forEach((action) => {
      console.log(this.name + " likes to " + action);
    });
  },
};

person.printActions();

Output:

Diksha likes to bike 
Diksha likes to hike 
Diksha likes to ski

⚝ Try this example on CodeSandbox

8. Destructing Assignment:

// Destructing Assignment
const { title, price, description } = {
  title: "iPhone",
  price: 999,
  description: "The iPhone is a smartphone developed by Apple"
};

console.log(title); // iPhone
console.log(price); // 999
console.log(description); // The iPhone is a smartphone developed by Apple

⚝ Try this example on CodeSandbox

9. Generators:

A generator is a function that can stop midway and then continue from where it stopped. In short, a generator appears to be a function but it behaves like an iterator.

function* generator(num) {
  yield num + 10;
  yield num + 20;
  yield num + 30;
}
const gen = generator(10);

console.log(gen.next().value); // 20
console.log(gen.next().value); // 30
console.log(gen.next().value); // 40

⚝ Try this example on CodeSandbox

10. Symbols:

They are tokens that serve as unique IDs. We create symbols via the factory function Symbol(). Symbols primary use case is for making private object properties, which can be only of type String or Symbol (Numbers are automatically converted to Strings).

const symbol1 = Symbol();
const symbol2 = Symbol(42);
const symbol3 = Symbol("Hi");

console.log(typeof symbol1); // symbol
console.log(symbol3.toString()); // Symbol(Hi)
console.log(Symbol("Hi") === Symbol("Hi")); // false

⚝ Try this example on CodeSandbox

11. Iterator:

The iterable is a interface that specifies that an object can be accessible if it implements a method who is key is [symbol.iterator].

const title = "ES6";
const iterateIt = title[Symbol.iterator]();

console.log(iterateIt.next().value); //output: E
console.log(iterateIt.next().value); //output: S
console.log(iterateIt.next().value); //output: 6

⚝ Try this example on CodeSandbox

# 2. VARIABLES


Q. What are global variables?

Global variables are declared outside of a function or declared with a window object for accessibility throughout the program (unless shadowed by locals). If you declare a variable without using var, even if it's inside a function, it will still be seen as global.

The var statement declares a function-scoped or globally-scoped variable, optionally initializing it to a value.

Example:

var x = 10;

if (x === 10) {
  var x = 20;

  console.log(x); // expected output: 20
}

console.log(x); // expected output: 20

Example: Declaring global variable within function

window.value = 90;

// Declaring global variable by window object
function setValue() {
  window.value = 100;
}

// Accessing global variable from other function
function getValue() {
  setValue();
  return window.value;
}

console.log(getValue()); // 100

Using Undeclared Variables:

  • In strict mode, if you attempt to use an undeclared variable, you'll get a reference error when you run your code.
  • Outside of strict mode, however, if you assign a value to a name that has not been declared with let, const, or var, you'll end up creating a new global variable. It will be global no matter how deeply nested within functions and blocks your code is, which is almost certainly not what you want, is bug-prone, and is one of the best reasons for using strict mode!
  • Global variables created in this accidental way are like global variables declared with var: they define properties of the global object. But unlike the properties defined by proper var declarations, these properties can be deleted with the delete operator.

⚝ Try this example on CodeSandbox

Q. What are template literals in es6?

Template literals help make it simple to do string interpolation, or to include variables in a string.

const person = { name: 'Tyler', age: 28 };

console.log(`Hi, my name is ${person.name} and I am ${person.age} years old!`);
// 'Hi, my name is Tyler and I am 28 years old!'

Template literals, however, preserve whatever spacing you add to them. For example, to create that same multi-line output that we created above, you can simply do:

console.log(`This is line one.
This is line two.`);
// This is line one.
// This is line two.

Another use case of template literals would be to use as a substitute for templating libraries for simple variable interpolations:

const person = { name: 'Tyler', age: 28 };

document.body.innerHTML = `
  <div>
    <p>Name: ${person.name}</p>
    <p>Name: ${person.age}</p>
  </div>
`

Q. What are the differences between variables created using let, var or const?

Variables declared using the var keyword are scoped to the function in which they are created, or if created outside of any function, to the global object. let and const are block scoped, meaning they are only accessible within the nearest set of curly braces (function, if-else block, or for-loop).

/**
 * All variables are accessible within functions.
 */
function variableScope() {

  var x = 10;
  let y = 20;
  const z = 30;

  console.log(x); // 10
  console.log(y); // 20
  console.log(z); // 30
}

console.log(x); // ReferenceError: x is not defined
console.log(y); // ReferenceError: y is not defined
console.log(z); // ReferenceError: z is not defined

variableScope();
/**
 * var declared variables are accessible anywhere in the function scope.
 */
if (true) {
  var a = 10;
  let b = 20;
  const c = 30;
}

console.log(a); // 10
console.log(b); // ReferenceError: b is not defined
console.log(c); // ReferenceError: c is not defined

var allows variables to be hoisted, meaning they can be referenced in code before they are declared. let and const will not allow this, instead throwing an error.

console.log(a); // undefined
var a = 'foo';

console.log(b); // ReferenceError: can\'t access lexical declaration 'b' before initialization
let b = 'baz';

console.log(c); // ReferenceError: can\'t access lexical declaration 'c' before initialization
const c = 'bar';

Redeclaring a variable with var will not throw an error, but 'let' and 'const' will.

var a = 'foo';
var a = 'bar';
console.log(a); // "bar"

let b = 'baz';
let b = 'qux'; // Uncaught SyntaxError: Identifier 'b' has already been declared

let and const differ in that let allows reassigning the variable's value while const does not.

// This is ok.
let a = 'foo';
a = 'bar';
console.log(a); // bar

// This causes an exception.
const b = 'baz';
b = 'qux';
console.log(b) // TypeError: Assignment to constant variable.

⚝ Try this example on CodeSandbox

Q. What is the purpose of double exclamation?

The double exclamation mark (!!) in JavaScript forces any value into its corresponding strict boolean representation (true or false). The !! (double negation) operator converts any value to its boolean equivalent:

  • First ! negates the value (converting it to a boolean)
  • Second ! negates it again, giving the true boolean representation

Example:

console.log(!!0);         // false
console.log(!!"");        // false
console.log(!!null);      // false
console.log(!!undefined); // false
console.log(!!NaN);       // false

console.log(!!1);         // true
console.log(!!"text");    // true
console.log(!!{});        // true
console.log(!![]);        // true

Note: It's commonly used to explicitly cast a value to boolean without using Boolean(value).

Q. In JavaScript, what is the difference between var x = 1 and x = 1?

The primary difference is that var x = 1 explicitly declares a function-scoped or globally-scoped variable, whereas x = 1 assigns a value to an identifier without declaring it, which can lead to global scope pollution or runtime errors depending on your code's strictness mode

var x = 1:

  • Allowed in 'strict mode'.
  • The var statement declares a function-scoped or globally-scoped variable, optionally initializing it to a value.
  • Variables declared using var inside a { } block can be accessed from outside the block.
  • Variables defined using var inside a function are not accessible (visible) from outside the function.
  • Duplicate variable declarations using var will not trigger an error, even in strict mode, and the variable will not lose its value unless another assignment is performed.

Example:

var x = 1;

if (x === 1) {
  var x = 2;

  console.log(x); // expected output: 2
}

console.log(x); // expected output: 2
var x = 5; // global
function someThing(y) {
  var x = 3; // local
  var z = x + y;
  console.log(z);
}
someThing(4); // 7
console.log(x); // 5

x = 1:

  • Not allowed in 'strict mode'.
  • Undeclared Variables like: x = 1 is accessible in: (Block scope - Function scope - Global scope)
  • Outside of strict mode, however, if you assign a value to a name that has not been declared with let, const, or var, you'll end up creating a new global variable. It will be global no matter how deeply nested within functions and blocks your code is, which is almost certainly not what you want, is bug-prone, and is one of the best reasons for using strict mode!
  • Global variables created in this accidental way are like global variables declared with var they define properties of the global object.
  • Unlike the properties defined by proper var declarations, these properties can be deleted with the delete operator.
  • Not recommended.

Example:

var x = 5; // global
function someThing(y) {
  x = 1; // still global!
  var z = x + y;
  console.log(z);
}
someThing(4) // 5
console.log(x) // 1

Example:

{
  console.log(x + y); // NaN
  var x = 1;
  var y = 2;
}
{
  console.log(x + y); // Uncaught ReferenceError: x is not defined
   x = 1;
   y = 2;
}

var x = 1x = 1
Strict mode✔❌
Block scope❌✔
Function scope✔✔
Global scope✔✔
Hoisting✔❌
Reassigning✔✔

Q. How do you assign default values to variables?

In JavaScript, you can assign default values to variables using several modern and legacy approaches, depending on the exact conditions you want to check.

1. OR operator (||)

let name = userInput || "Default";
// Uses "Default" if userInput is falsy (0, "", null, undefined, false)

2. Nullish coalescing (??) (preferred)

let name = userInput ?? "Default";
// Uses "Default" only if userInput is null or undefined (not 0 or "")

3. Destructuring defaults

const { name = "John", age = 25 } = user;
const [first = 0, second = 0] = arr;

4. Function parameter defaults

function greet(name = "World") {
  return `Hello, ${name}!`;
}
greet();        // "Hello, World!"
greet("Alice"); // "Hello, Alice!"

5. Logical OR assignment (||=)

let x = null;
x ||= "default"; // x = "default"

6. Nullish assignment (??=)

let x = 0;
x ??= "default"; // x stays 0 (not null/undefined)

Key difference between || and ??:

let count = 0;
count || 10;  // 10  — 0 is falsy, so falls back
count ?? 10;  // 0   — 0 is not null/undefined, so keeps it

Note: Use ?? when 0, "", or false are valid values.

Q. What is the precedence order between local and global variables?

Local variables always take precedence over global variables within their scope.

let x = "global";

function example() {
  let x = "local";
  console.log(x); // "local" — local shadows the global
}

example();
console.log(x); // "global" — unchanged

Scope chain — JavaScript looks up variables from innermost to outermost:

let x = "global";

function outer() {
  let x = "outer";

  function inner() {
    let x = "inner";
    console.log(x); // "inner"
  }

  inner();
  console.log(x); // "outer"
}

outer();
console.log(x); // "global"

If no local variable exists, it walks up the chain:

let x = "global";

function example() {
  console.log(x); // "global" — no local x, found in outer scope
}

Shadowing pitfall:

let x = "global";

function example() {
  console.log(x); // ReferenceError (TDZ) — let/const are hoisted but not initialized
  let x = "local";
}

Precedence order (innermost → outermost):

  1. Local (block/function) scope
  2. Enclosing/closure scopes
  3. Module scope
  4. Global scope (window / globalThis)

⚝ Try this example on CodeSandbox

Q. What is variable shadowing in javascript?

Variable shadowing occurs when a variable declared within a certain scope (decision block, method, or inner class) has the same name as a variable declared in an outer scope. This outer variable is said to be shadowed.

If there is a variable in the global scope, and you'd like to create a variable with the same name in a function. The variable in the inner scope will temporarily shadow the variable in the outer scope.

Example:

let x = "global";

function example() {
  let x = "local"; // shadows the global x
  console.log(x);  // "local"
}

example();
console.log(x); // "global" — outer x is unaffected

Block-level shadowing (let/const):

let msg = "outside";

if (true) {
  let msg = "inside"; // shadows outer msg
  console.log(msg);   // "inside"
}

console.log(msg); // "outside"

var does NOT shadow at block level (only at function level):

var msg = "outside";

if (true) {
  var msg = "inside"; // overwrites, not shadows!
  console.log(msg);   // "inside"
}

console.log(msg); // "inside" — original was overwritten!

Nested function shadowing:

let value = 1;

function outer() {
  let value = 2;        // shadows global

  function inner() {
    let value = 3;      // shadows outer
    console.log(value); // 3
  }

  inner();
  console.log(value);   // 2
}

outer();
console.log(value);     // 1

Note:

  • Shadowing does not modify the outer variable
  • let/const shadow at block level; var only at function level
  • Can cause bugs when unintentional — use distinct variable names to avoid confusion

Q. What is scope chain in javascript?

The scope chain is the mechanism JavaScript uses to resolve variable references by searching through nested scopes from innermost to outermost until the variable is found (or a ReferenceError is thrown).

Example::

let globalVar = "I'm a global variable";

function outer() {
  let outerVar = "I'm an outer variable";

  function inner() {
    let innerVar = "I'm an inner variable";
    console.log(innerVar); // "I'm an inner variable"
    console.log(outerVar); // "I'm an outer variable"
    console.log(globalVar); // "I'm a global variable"
  }

  inner();
}

outer();

Note:

  • The chain is created at function definition time (lexical scoping), not at call time
  • It only goes outward — outer scopes cannot access inner variables
  • Closures work because inner functions retain a reference to their outer scope chain

# 3. DATA TYPES


Q. What are data types in javascript?

There are eight basic data types in JavaScript.

Data TypesDescriptionExample
StringRepresents textual datalet str = 'Hi', let str2 = "Hello", let str3 = `Hello World`
NumberAn integer or a floating-point numberlet num = 3, let num2 = 3.234, let num3 = 3e-2
BigIntAn integer with arbitrary precisionlet num = 900719925124740999n, let num = 1n
BooleanAny of two values: true or falselet flag = true
undefinedA data type whose variable is not initializedlet a;
nullDenotes a null valuelet a = null;
SymbolData type whose instances are unique and immutablelet value = Symbol('hello');
Objectkey-value pairs of collection of datalet student = { };

String:

String is used to store text. In JavaScript, strings are surrounded by quotes:

  • Single quotes: 'Hello'
  • Double quotes: "Hello"
  • Backticks: `Hello`

Example::

// Strings
const firstName = "Mukul";
const lastName = "Mittal";
const result = `Name: ${firstName} ${lastName}`;

console.log(result); // Name: Mukul Mittal

console.log(typeof result); // string

Number:

Number represents integer and floating numbers (decimals and exponentials). A number type can also be +Infinity, -Infinity, and NaN (not a number).

const number1 = 3;
const number2 = 3.433;
const number3 = 3e5; // 3 * 10^5

const number4 = 3 / 0;
console.log(number4); // Infinity
console.log(typeof number4) // number

const number5 = -3 / 0;
console.log(number5); // -Infinity
console.log(typeof number5); // number

// strings can\'t be divided by numbers
const number6 = "abc" / 3;
console.log(number6); // NaN
console.log(typeof number6); // number

BigInt:

In JavaScript, Number type can only represent numbers less than (253 - 1) and more than -(253 - 1). However, if you need to use a larger number than that, you can use the BigInt data type.

A BigInt number is created by appending n to the end of an integer.

// BigInt value
const num1 = 100000000000000000n;
const num2 = 1000000000000000000n;
const num3 = 10;

// Adding two big integers
const result1 = num1 + num2;
console.log(result1); // "1100000000000000000n"
console.log(typeof result1); // bigint

// Error! BitInt and number cannot be added
const result2 = num1 + num2 + num3;
console.log(result2); // Uncaught TypeError: Cannot mix BigInt and other types
console.log(typeof result2); // Uncaught TypeError: Cannot mix BigInt and other types

Boolean:

This data type represents logical entities. Boolean represents one of two values: true or false.

const dataChecked = true;
const valueCounted = false;

undefined:

The undefined data type represents value that is not assigned. If a variable is declared but the value is not assigned, then the value of that variable will be undefined.

let name;
console.log(name); // undefined

let name = undefined;
console.log(name); // undefined

null:

In JavaScript, null is a special value that represents empty or unknown value.

const number = null;

Symbol:

A value having the data type Symbol can be referred to as a symbol value. Symbol is an immutable primitive value that is unique.

// Two symbols with the same description

const value1 = Symbol('hello');
const value2 = Symbol('hello');

let result = (value1 === value2) ? true : false;  // false;

// Note: Though value1 and value2 both contain 'hello', they are different as they are of the Symbol type.

Object:

An object is a complex data type that allows us to store collections of data.

const employee = {
    firstName: 'John',
    lastName: 'K',
    email: 'john.k@gmail.com'
};

Q. What is undefined property?

The undefined property indicates that a variable has not been assigned a value, or not declared at all. The type of undefined value is undefined too.

var user;    // Value is undefined, type is undefined
console.log(typeof(user)) //undefined

Any variable can be emptied by setting the value to undefined.

user = undefined

Q. What is difference between null vs undefined?

Null:

Null means an empty or non-existent value. Null is assigned, and explicitly means nothing.

var test = null;
console.log(test); // null

null is also an object. Interestingly, this was actually an error in the original JavaScript implementation:

console.log(typeof test); // object

Undefined:

Undefined means a variable has been declared, but the value of that variable has not yet been defined. For example:

var test2;
console.log(test2); // undefined

Unlike null, undefined is of the type undefined:

console.log(typeof test2); // undefined

Difference:

NullUndefined
It is an assignment value which indicates that variable points to no object.It is not an assignment value where a variable has been declared but has not yet been assigned a value.
Type of null is objectType of undefined is undefined
The null value is a primitive value that represents the null, empty, or non-existent reference.The undefined value is a primitive value used when a variable has not been assigned a value.
Indicates the absence of a value for a variableIndicates absence of variable itself
Converted to zero (0) while performing primitive operationsConverted to NaN while performing primitive operations

Q. What is Coercion in JavaScript?

Type coercion is the automatic or implicit conversion of values from one data type to another (such as strings to numbers). Type conversion is similar to type coercion because they both convert values from one data type to another with one key difference — type coercion is implicit whereas type conversion can be either implicit or explicit.

Example:

const value1 = '10';
const value2 = 20;

let sum = value1 + value2;

console.log(sum);

In the above example, JavaScript has coerced the 10 from a number into a string and then concatenated the two values together, resulting in a string of 1020. JavaScript had a choice between a string or a number and decided to use a string.

// Example of explicit coercion
const value1 = '10';
const value2 = 20;

let sum = Number(value1) + value2;

console.log(sum);

# 4. OPERATORS


Q. What are various operators supported by javascript?

An operator is capable of manipulating (mathematical and logical computations) a certain value or operand. There are various operators supported by JavaScript as below,

Arithmetic Operators:

Arithmetic operators are used to perform mathematical operations between numeric operands.

OperatorsDescriptionExample ( say let x = 10, y = 20;)
+Adds two numeric operands.x + y
-Subtract right operand from left operandy - x
*Multiply two numeric operands.x * y
/Divide left operand by right operand.y / x
%Modulus operator. Returns remainder of two operands.x % 2
++Increment operator. Increase operand value by one.x++
--Decrement operator. Decrease value by one.x--

Comparison Operators:

JavaScript provides comparison operators that compare two operands and return a boolean value true or false.

OperatorsDescription
==Compares the equality of two operands without considering type.
===Compares equality of two operands with type.
!=Compares inequality of two operands.
>Returns a boolean value true if the left-side value is greater than the right-side value; otherwise, returns false.
<Returns a boolean value true if the left-side value is less than the right-side value; otherwise, returns false.
>=Returns a boolean value true if the left-side value is greater than or equal to the right-side value; otherwise, returns false.
<=Returns a boolean value true if the left-side value is less than or equal to the right-side value; otherwise, returns false.

Logical Operators:

The logical operators are used to combine two or more conditions.

1. && - is known as AND operator. It checks whether two operands are non-zero or not (0, false, undefined, null or "" are considered as zero). It returns 1 if they are non-zero; otherwise, returns 0

2. || - is known as OR operator. It checks whether any one of the two operands is non-zero or not (0, false, undefined, null or "" is considered as zero). It returns 1 if any one of of them is non-zero; otherwise, returns 0.

3. ! - is known as NOT operator. It reverses the boolean result of the operand (or condition). !false returns true, and !true returns false.

Assignment Operators:

The assignment operators to assign values to variables with less key strokes.

OperatorsDescription
=Assigns right operand value to the left operand.
+=Sums up left and right operand values and assigns the result to the left operand.
-=Subtract right operand value from the left operand value and assigns the result to the left operand.
*=Multiply left and right operand values and assigns the result to the left operand.
/=Divide left operand value by right operand value and assign the result to the left operand.
%=Get the modulus of left operand divide by right operand and assign resulted modulus to the left operand.

Q. What are the bitwise operators available in javascript?

Below are the list of bit-wise logical operators used in JavaScript

OperatorUsageDescription
Bitwise ANDa & bReturns a one in each bit position for which the corresponding bits of both operands are ones.
Bitwise ORab
Bitwise XORa ^ bReturns a one in each bit position for which the corresponding bits of either but not both operands are ones.
Bitwise NOT~ aInverts the bits of its operand.
Left shifta << bShifts a in binary representation b (< 32) bits to the left, shifting in zeroes from the right.
Sign-propagating right shifta >> bShifts a in binary representation b (< 32) bits to the right, discarding bits shifted off.
Zero-fill right shifta >>> bShifts a in binary representation b (< 32) bits to the right, discarding bits shifted off, and shifting in zeroes from the left.

Examples:

OperationResultSame asResult
5 & 110101 & 00010001
`51`50101
~ 5-6~01011010
5 << 1100101 << 11010
5 ^ 140101 ^ 00010100
5 >> 120101 >> 10010
5 >>> 120101 >>> 10010

⚝ Try this example on CodeSandbox

Q. What is the difference between == and === operators?

JavaScript provides both strict (===, !==) and type-converting (==, !=) equality comparison. The strict operators takes type of variable in consideration, while non-strict operators make type correction/conversion based upon values of variables. The strict operators follow the below conditions for different types,

  1. Two strings are strictly equal when they have the same sequence of characters, same length, and same characters in corresponding positions.
  2. Two numbers are strictly equal when they are numerically equal. i.e, Having the same number value. There are two special cases in this,
    1. NaN is not equal to anything, including NaN.
    2. Positive and negative zeros are equal to one another.
  3. Two Boolean operands are strictly equal if both are true or both are false.
  4. Two objects are strictly equal if they refer to the same Object.
  5. Null and Undefined types are not equal with ===, but equal with ==. i.e, null === undefined --> false but null == undefined --> true

Example:

0 == false // true
0 === false // false
1 == "1" // true
1 === "1" // false
null == undefined // true
null === undefined // false
"0" == false // true
"0" === false // false
[] === [] // false, refer different objects in memory
{} === {} // false, refer different objects in memory

⚝ Try this example on CodeSandbox

Q. What is typeof operator?

In JavaScript, the typeof operator returns the data type of its operand in the form of a string. The operand can be any object, function, or variable.

Example:

console.log(typeof undeclaredVariable); // "undefined"

let a;
console.log(typeof a); // "undefined"

const b = "Hello World";
console.log(typeof b); // "string"

const c = 42;
console.log(typeof c); // "number"

const d = 3.1415;
console.log(typeof d); // "number"

const e = true;
console.log(typeof e); // "boolean"

const f = null;
console.log(typeof f); // "object"

const g = undefined;
console.log(typeof g); // "undefined"

const h = { b: "c" };
console.log(typeof h); // "object"

const i = function () {
  return 10;
};

console.log(typeof i); // "function"

⚝ Try this example on CodeSandbox

Q. What is an Unary operator?

The unary(+) operator is used to convert a variable to a number. If the variable cannot be converted, it will still become a number but with the value NaN.

Example:

var x = "100";
var y = +x;
console.log(typeof x, typeof y); // string, number

var a = "Hello";
var b = +a;
console.log(typeof a, typeof b, b); // string, number, NaN

⚝ Try this example on CodeSandbox

Q. What is the purpose of delete operator?

The delete keyword is used to delete the property as well as its value.

Example:

const user = { name: "Sadhika Chaudhuri", age: 24 };
delete user.age;

console.log(user); // { name: "Sadhika Chaudhuri" }

Q. What is a conditional operator in javascript?

The conditional (ternary) operator is the only JavaScript operator that takes three operands which acts as a shortcut for if statement.

Syntax:

<condition> ? <value1> : <value2>;

Example:

const isAuthenticated = false;

console.log(isAuthenticated ? 'Hello, welcome' : 'Sorry, you are not authenticated');

Q. Can you apply chaining on conditional operator?

Yes, you can apply chaining on conditional operator similar to if … else if … else if … else chain.

Example:

function getValue(someParam) {
  return condition1 ? value1
    : condition2 ? value2
    : condition3 ? value3
    : value4;
}

// The above conditional operator is equivalent to:
function getValue(someParam) {
  if (condition1) {
    return value1;
  } else if (condition2) {
    return value2;
  } else if (condition3) {
    return value3;
  } else {
    return value4;
  }
}

Q. What is the difference between typeof and instanceof operator?

The typeof operator checks if a value has type of primitive type which can be one of boolean, function, object, number, string, undefined, symbol, or bigint.

Example:

const x = "Hello World";
const y = new String("Hello World");

typeof x; // returns 'string'
typeof y; // returns 'object'

The instanceof is a binary operator, accepting an object and a constructor. It returns a boolean indicating whether or not the object has the given constructor in its prototype chain.

const a = "Hello World";
const b = new String("Hello World");

a instanceof String; // returns false
b instanceof String; // returns true

⚝ Try this example on CodeSandbox

Q. What is the output of below spread operator array?

[...'Hello']

Output: ['H', 'e', 'l', 'l', 'o']

Explanation: The string is an iterable type and the spread operator with in an array maps every character of an iterable to one element. Hence, each character of a string becomes an element within an Array.

Q. What is the nullish coalescing operator (??) in JavaScript?

The nullish coalescing operator (??) returns the right-hand side operand when the left-hand side operand is null or undefined. Otherwise, it returns the left-hand side operand. Unlike ||, it does not treat 0, false, or "" as falsy.

Syntax:

leftExpr ?? rightExpr

Example:

console.log(null ?? 'default');      // 'default'
console.log(undefined ?? 'default'); // 'default'
console.log(0 ?? 'default');         // 0  (0 is NOT null/undefined)
console.log('' ?? 'default');        // '' (empty string is NOT null/undefined)
console.log(false ?? 'default');     // false

// Practical use: provide a fallback for a missing config value
const config = { timeout: 0 };
const timeout = config.timeout ?? 3000;
console.log(timeout); // 0 (not 3000, because 0 is a valid value)

Nullish coalescing assignment (??=):

let user = { name: null };
user.name ??= 'Anonymous';
console.log(user.name); // 'Anonymous'

Q. What are logical assignment operators in JavaScript?

Logical assignment operators (ES2021) combine a logical operator with assignment. There are three:

OperatorEquivalent toDescription
&&=a && (a = b)Assigns b to a only if a is truthy
||=a || (a = b)Assigns b to a only if a is falsy
??=a ?? (a = b)Assigns b to a only if a is null or undefined

Example:

// &&= (AND assignment)
let a = 1;
a &&= 2;
console.log(a); // 2 (a was truthy, so assigned)

let b = 0;
b &&= 2;
console.log(b); // 0 (b was falsy, not assigned)

// ||= (OR assignment)
let c = null;
c ||= 'default';
console.log(c); // 'default' (c was falsy)

let d = 'existing';
d ||= 'default';
console.log(d); // 'existing' (d was truthy, not reassigned)

// ??= (Nullish assignment)
let e = null;
e ??= 'fallback';
console.log(e); // 'fallback'

let f = 0;
f ??= 'fallback';
console.log(f); // 0 (0 is not null/undefined)

# 5. NUMBERS


Q. How do you generate random integers?

The Math.random() function returns a floating-point, pseudo-random number in the range 0 to less than 1 (inclusive of 0, but not 1). For example, if you want generate random integers between 1 to 100, the multiplication factor should be 100,

// Example 01:
Math.random(); // returns a random integer between 0 to 1

// Example 02:
Math.floor(Math.random() * 100) + 1; // returns a random integer from 1 to 100

// Example 03:
function getRandomNumber(max) {
  return Math.floor(Math.random() * max) + 1;
}

console.log(getRandomNumber(10)); // returns a random integer from 1 to 10

Random integer between min and max (inclusive):

function randomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

console.log(randomInt(1, 6));   // simulates a dice roll: 1–6

⚝ Try this example on CodeSandbox

Q. What is isNaN?

The isNaN() function determines whether a value is NaN ( Not a Number ) or not. This function returns true if the value equates to NaN. The isNaN() method converts the value to a number before testing it.

Example:

isNaN('Hello') // true

isNaN('100') // false

typeof NaN // Number

Number.isNaN('Hello'); // false

⚝ Try this example on CodeSandbox

Q. What is the purpose of isFinite function?

The global isFinite() function determines whether the passed value is a finite number. It returns false if the value is +infinity, -infinity, or NaN (Not-a-Number), otherwise it returns true.

isFinite(Infinity);  // false
isFinite(NaN);       // false
isFinite(-Infinity); // false

isFinite(100);  // true
isFinite(1/0); // false

Number.isFinite(0 / 0); // false
Number.isFinite(null); // false
Number.isFinite("123") // false

⚝ Try this example on CodeSandbox

Q. Explain NEGATIVE_INFINITY in JavaScript?

The Number.NEGATIVE_INFINITY property represents the negative Infinity value.

Syntax:

Number.NEGATIVE_INFINITY
  • Negative infinity is a number in javascript, which is derived by 'dividing negative number by zero'.
  • A number object needs not to be created to access this static property.
  • The value of negative infinity is the same as the negative value of the infinity property of the global object.
/**
 * NEGATIVE_INFINITY
 */

console.log(-10/0); // -Infinity
console.log(Number.NEGATIVE_INFINITY); // -Infinity
console.log(Number.MAX_VALUE + Number.MAX_VALUE); // Infinity
console.log(-2 * Number.MAX_VALUE); // -Infinity

console.log("Math.pow(10, 1000): " + Math.pow(10, 1000)); // Infinity 
console.log("Math.log(0): " + Math.log(0)); // -Infinity

console.log(Number.NEGATIVE_INFINITY === -2 * Number.MAX_VALUE); // true

⚝ Try this example on CodeSandbox

# 6. STRING


Q. What is the difference between slice and splice?

1. slice():

The slice() method returns a new array with a copied slice from the original array. The first optional argument is the beginning index and the second optional argument is the ending index (non-inclusive).

Example:

let languages = [ "JavaScript", "Python", "Java", "PHP" ];

languages.slice(1,3); // ["Python", "Java"]
languages.slice(2); // (from index 2 until the end of the array).
// ["Java", "PHP"]

console.log(languages); // the original array is not mutated.
// [ "JavaScript", "Python", "Java", "PHP" ]

2. splice():

The splice() method changes the content of the array in place and can be used to add or remove items from the array. When only one argument is provided, all the items after the provided starting index are removed from the array.

Example:

let numbers = [10, 20, 30];

numbers.splice(2, 1, 40, 50); // returns removed array:[30]

console.log(numbers); // Original array is mutated.
// returns: [10, 20, 40, 50]

Difference:

SliceSplice
Doesn't modify the original array(immutable)Modifies the original array(mutable)
Returns the subset of original arrayReturns the deleted elements as array
Used to pick the elements from arrayUsed to insert or delete elements to/from array

⚝ Try this example on CodeSandbox

Q. How do you check whether a string contains a substring?

There are 3 fastest ways to check whether a string contains a substring or not,

1. Using RegEx:

The regular expression test() method checks if a match exists in a string. This method returns true if it finds a match, otherwise, it returns false.

let str = "JavaScript, Node.js, Express.js, React.js, MongoDB";
let exp1 = /MongoDB/g;
let exp2 = /Ajax/;

exp1.test(str); // true
exp2.test(str); // false

2. Using indexOf:

The indexOf() method is case-sensitive and accepts two parameters. The first parameter is the substring to search for, and the second optional parameter is the index to start the search from (default index is 0).

let str = "JavaScript, Node.js, Express.js, React.js, MongoDB";

str.indexOf('MongoDB') !== -1 // true
str.indexOf('PHP') !== -1 // false
str.indexOf('Node', 5) !== -1 // true

3. Using includes: (Modern Standard)

The includes() is also case-sensitive and accepts an optional second parameter, an integer which indicates the position where to start searching for.

let str = "JavaScript, Node.js, Express.js, React.js, MongoDB";

str.includes('MongoDB') // true
str.includes('PHP') // false
str.includes('Node', 5) //true

Use Cases:

Use CaseMethodExample
Simple readability checkincludes()Check if a product description contains a keyword
Legacy browser supportindexOf()Check if URL contains a query param
Pattern-based searchRegEx test()Check if input matches a specific format
// 1. Search bar — check if item name contains user query
const query = "react";
const items = ["React.js Tutorial", "Node.js Guide", "React Native Basics"];
const results = items.filter(item => item.toLowerCase().includes(query.toLowerCase()));
// ["React.js Tutorial", "React Native Basics"]

// 2. URL parsing — check if a query param exists
const url = "https://example.com/search?q=javascript&page=2";
const hasQuery = url.indexOf('?q=') !== -1; // true

// 3. Input validation — check if email contains '@' and '.'
const email = "user@example.com";
const isValidEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); // true

⚝ Try this example on CodeSandbox

Q. How do you trim a string in javascript?

The trim() method removes whitespace from both sides of a string. JavaScript provides 3 simple functions on how to trim strings.

1. string.trim():

The string.trim() removes sequences of whitespaces and line terminators from both the start and the end of the string.

const name = "  Karan Talwar  ";
console.log(name.trim()); // => 'Karan Talwar'

const phoneNumber = "\t  80-555-123\n ";
console.log(phoneNumber.trim()); // => '80-555-123'

2. string.trimStart():

The string.trimStart() removes sequences of whitespaces and line terminators only from the start of the string.

const name = "   Karan Talwar  ";
console.log(name.trimStart()); // => "Karan Talwar  "

const phoneNumber = "\t  80-555-123\n ";
console.log(phoneNumber.trimStart()); // => "80-555-123 "

3. string.trimEnd():

The string.trimEnd() removes sequences of whitespaces and line terminators only from the end of the string.

const name = "  Karan Talwar ";
console.log(name.trimEnd()); // => " Karan Talwar"

const phoneNumber = "\t  80-555-123\n ";
console.log(phoneNumber.trimEnd()); // => " 80-555-123"

⚝ Try this example on CodeSandbox

Q. What is eval function in javascript?

The eval() function evaluates JavaScript code represented as a string. The string can be a JavaScript expression, variable, statement, or sequence of statements.

console.log(eval('10 + 20')); // 30

let x = 10;
let y = 20;
let z = '50';
eval('x + y + 1'); // returns 31
eval(z);           // returns 50

If the argument of eval() is not a string, eval() returns the argument unchanged. In the following example, the String constructor is specified and eval() returns a String object rather than evaluating the string.

eval(new String('10 + 20')); // returns a String object containing "10 + 20"
eval('10 + 20');             // returns 30


// work around
let expression = new String('10 + 20');
eval(expression.toString()); // returns 30

Warning: Executing JavaScript from a string is an enormous security risk. It is far too easy for a bad actor to run arbitrary code when you use eval().

⚝ Try this example on CodeSandbox

Q. How do you check if a string starts with another string?

You can use String.prototype.startsWith() method to check if a string starts with another string or not. It is fully supported in all modern browsers.

let str = "Hello World";

console.log(str.startsWith("Hello")); // true
console.log(str.startsWith("World")); // false

⚝ Try this example on CodeSandbox

Q. What are replaceAll(), padStart() and padEnd() string methods?

1. String.prototype.replaceAll():

The replaceAll() method returns a new string with all matches of a pattern replaced by a replacement. Unlike replace(), it replaces every occurrence without needing a global regex flag.

const text = 'I like cats. Cats are cute cats.';
console.log(text.replace('cats', 'dogs'));    // 'I like dogs. Cats are cute cats.' (only first)
console.log(text.replaceAll('cats', 'dogs')); // 'I like dogs. Cats are cute dogs.'

2. String.prototype.padStart():

The padStart() method pads the current string with another string from the start until it reaches the given length.

console.log('5'.padStart(3, '0'));    // '005'
console.log('hello'.padStart(8));     // '   hello' (default pad is space)
console.log('42'.padStart(5, '*'));   // '***42'

3. String.prototype.padEnd():

The padEnd() method pads the current string with another string from the end until it reaches the given length.

console.log('5'.padEnd(3, '0'));    // '500'
console.log('hello'.padEnd(8));     // 'hello   '
console.log('42'.padEnd(5, '-'));   // '42---'

# 7. ARRAY


Q. Explain arrays in JavaScript?

JavaScript array is an object that represents a collection of similar type of elements. It can holds values (of any type) not particularly in named properties/keys, but rather in numerically indexed positions.

Syntax:

const array_name = [item-1, item-2, item-3, ...];    

Example 01: Creating an array

// array of numbers
const numbers = [10, 20, 30, 40, 50];

// using new keyword
const numbers = new Array(10, 20, 30, 40, 50);

// array of strings
let fruits = ["Apple", "Orange", "Plum", "Mango"];

// array of character
const numbers = Array.from("abc"); // ["a", "b", "c"]

// array of numbers
const numbers = Array.of(10, 20, 30); // [10, 20, 30]

Common Methods

CategoryMethods
Add/Removepush, pop, shift, unshift, splice
Extractslice, flat, flatMap
Searchfind, findIndex, indexOf, includes
IterateforEach, map, filter, reduce
Sort/Ordersort, reverse
Combineconcat, join

Example 02: Accessing array elements

const numbers = [10, 20, 30, 40, 50];

// Transforming
console.log(numbers.map(n => n * 2)); // [20, 40, 60, 80, 100]

// Filtering
console.log(numbers.filter(n => n % 2 === 0)); // [10, 20, 30, 40, 50]

// Reducing
console.log(numbers.reduce((sum, n) => sum + n, 0)); // 150

// Searching
console.log(numbers.find(n => n > 30));  // 40
console.log(numbers.includes(30));  // true

Example 03: Adding new array elements

let fruits = ["Apple", "Orange", "Plum", "Mango"];

fruits.push("Grapes");  // Adds a new element (Grapes) to fruits

⚝ Try this example on CodeSandbox

Q. What are associative arrays in javascript?

JavaScript does not have built-in associative arrays in the way languages like PHP or literal dictionary structures do. Instead, JavaScript uses regular Objects or Maps to handle key-value pairs (associative behavior).

Associative arrays are basically objects in JavaScript where indexes are replaced by user-defined keys. They do not have a length property like a normal array and cannot be traversed using a normal for loop.

Syntax:

const array_name = { key1: 'value1', key2: 'value2', key3: 'value3' }   

Example: Plain Object (most common)

const employee = {
  id: 12345,
  name: "Sakshi Memon",
  email: "sakshi.memon@email.com"
};

// Accessing employee elements
console.log(employee.id); // 12345
console.log(employee.name); // Sakshi Memon

// Array Length 
console.log(Object.keys(employee).length); // 3

// Retrieve the elements
for (let key in employee) {
  console.log(key + " = " + employee[key]);
}

// Output
id = 12345 
name = Sakshi Memon 
email = sakshi.memon@email.com 

Example: Map (true associative structure)

const map = new Map();
map.set("name", "Alice");
map.set(42, "answer");        // keys can be any type
map.set({ id: 1 }, "object key");

map.get("name");  // "Alice"
map.has(42);      // true
map.size;         // 3

⚝ Try this example on CodeSandbox

Q. How to calculate the length of the associative array?

Method 1: Using Object.keys().length

const employee = {
  id: 12345,
  name: "Sakshi Memon",
  email: "sakshi.memon@email.com"
};

console.log(Object.keys(employee).length); // 3

Method 2: Using Object.hasOwnProperty()

function getLength(object) {
  let count = 0;
  for (let key in object) {
    // hasOwnProperty method check own property of object
    if (object.hasOwnProperty(key)) count++;
  }
  return count;
}

console.log(getLength(employee)); // 3

Method 3: Using Object.getOwnPropertyNames()

const employee = {
  id: 12345,
  name: "Sakshi Memon",
  email: "sakshi.memon@email.com"
};

Object.getOwnPropertyNames(employee).length; // 3

⚝ Try this example on CodeSandbox

Q. What is the difference between Array and Array of Objects in JavaScript?

Objects represent a special data type that is mutable and can be used to store a collection of data (rather than just a single value). Arrays are a special type of variable that is also mutable and can also be used to store a list of values.

Example: Arrays

const numbers = [10, 20, 30];

// Iterating through loop
for (let i = 0; i < numbers.length; i++) {
  console.log(numbers[i]);
}

// Pop an element from array
numbers.pop();
console.log("after pop(): " + numbers);

Example: Array of Objects

const employees = [
  { id: 101, name: "Sakshi Memon", email: "sakshi.memon@email.com" },
  { id: 102, name: "Subhash Shukla", email: "subhash.shukla@email.com" },
  { id: 103, name: "Mohini Karpe", email: "mohini.karpe@email.com" }
];

// Using DOT notation
console.log(employees[0].name);

// Using delete keyword
delete employees[0];

// Iterating using for..in loop
for (let key in employees) {
  console.log(employees[key]);
}

Difference:

S.No.ArrayArray of objects
1.Arrays are best to use when the elements are numbers.Objects are best to use when the elements strings
3.The elements can be manipulated using [].The properties can be manipulated using both . ( DOT ) notation and [].
4.The elements can be popped out of an array using the pop() function.The keys or properties can be deleted by using the delete keyword.
5.Iterating through an array is possible using For loop, For..in, For..of, and ForEach().Iterating through an array of objects is possible using For..in, For..of, and ForEach().

⚝ Try this example on CodeSandbox

Q. Explain array methods [ join(), pop(), push(), shift(), unshift(), concat(), map(), filter(), reduce(), reduceRight(), every(), some(), indexOf(), lastIndexOf(), find(), findIndex(), includes() ]

1. array.join():

The join() method creates and returns a new string by concatenating all of the elements in an array (or an array-like object), separated by commas or a specified separator string. If the array has only one item, then that item will be returned without using the separator.

var elements = ['Fire', 'Air', 'Water'];

console.log(elements.join()); // Output: "Fire,Air,Water"
console.log(elements.join('')); // Output: "FireAirWater"
console.log(elements.join('-')); // Output: "Fire-Air-Water"

2. array.pop():

The pop() method removes the last element from an array and returns that element. This method changes the length of the array.

var plants = ['broccoli', 'cauliflower', 'kale'];

console.log(plants.pop()); // Output: "kale"
console.log(plants); // Output: Array ["broccoli", "cauliflower"]
console.log(plants.pop()); // Output: "cauliflower"
console.log(plants.pop()); // Output: "broccoli"
console.log(plants.pop()); // Output: "undefined"

3. array.push():

The push() method adds one or more elements to the end of an array and returns the new length of the array.

const animals = ['pigs', 'goats', 'sheep'];

const count = animals.push('cows');
console.log(count); // Output: 4
console.log(animals); // Output: Array ["pigs", "goats", "sheep", "cows"]

4. array.shift():

The shift() method removes the first element from an array and returns that removed element. This method changes the length of the array.

var fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits.shift();
console.log(fruits) // Output: Array ["Orange", "Apple", "Mango"]

5. array.unshift():

The unshift() method adds one or more elements to the beginning of an array and returns the new length of the array.

var fruits = ["Banana", "Orange", "Apple"];
fruits.unshift("Mango","Pineapple");
console.log(fruits); // Output: Array ["Mango", "Pineapple", "Banana", "Orange", "Apple"]

6. array.concat():

The concat() method is used to merge two or more arrays. This method does not change the existing arrays, but instead returns a new array.

const array1 = ['a', 'b', 'c'];
const array2 = ['d', 'e', 'f'];

console.log(array1.concat(array2)); // Output: Array ["a", "b", "c", "d", "e", "f"]

7. array.map():

The map() method creates a new array with the results of calling a provided function on every element in the calling array.

var array1 = [1, 4, 9, 16];

// pass a function to map
const map1 = array1.map(x => x * 2); 

console.log(map1); // Output: Array [2, 8, 18, 32]

8. array.filter():

The filter() method creates a new array with all elements that pass the test implemented by the provided function.

var words = ['spray', 'limit', 'elite', 'exuberant', 'destruction'];

const result = words.filter(word => word.length > 6);

console.log(result); // Output: Array ["exuberant", "destruction"]

9. array.reduce():

The reduce() method executes a reducer function (that you provide) on each element of the array, resulting in a single output value.

const array1 = [1, 2, 3, 4];
const reducer = (accumulator, currentValue) => accumulator + currentValue;

console.log(array1.reduce(reducer)); // Output: 10
console.log(array1.reduce(reducer, 5)); // Output: 15

10. array.reduceRight():

The reduceRight() method applies a function against an accumulator and each value of the array (from right-to-left) to reduce it to a single value.

const array1 = [[0, 1], [2, 3], [4, 5]].reduceRight(
  (accumulator, currentValue) => accumulator.concat(currentValue)
);

console.log(array1); // Output: Array [4, 5, 2, 3, 0, 1]

11. array.every():

The every() method tests whether all elements in the array pass the test implemented by the provided function. It returns a Boolean value.

function isBelowThreshold(currentValue) {
  return currentValue < 40;
}

var array1 = [1, 30, 39, 29, 10, 13];
console.log(array1.every(isBelowThreshold)); // Output: true

12. array.some():

The some() method tests whether at least one element in the array passes the test implemented by the provided function. It returns a Boolean value.

var array = [1, 2, 3, 4, 5];

var even = function(element) {
  // checks whether an element is even
  return element % 2 === 0;
};

console.log(array.some(even)); // Output: true

13. array.indexOf():

The indexOf() method returns the first index at which a given element can be found in the array, or -1 if it is not present.

var beasts = ['ant', 'bison', 'camel'];

console.log(beasts.indexOf('camel')); // Output: 2
console.log(beasts.indexOf('giraffe')); // Output: -1

14. array.lastIndexOf():

The lastIndexOf() method returns the index within the calling String object of the last occurrence of the specified value, searching backwards from fromIndex. Returns -1 if the value is not found.

var paragraph = 'The quick brown fox jumps over the lazy dog. If the dog barked, was it really lazy?';

var searchTerm = 'dog';

console.log('The index of the first "' + searchTerm + '" from the end is ' + paragraph.lastIndexOf(searchTerm));
// Output: "The index of the first "dog" from the end is 52"

15. array.find():

The find() method returns the value of the first element in the provided array that satisfies the provided testing function.

var array1 = [5, 12, 8, 130, 44];

var found = array1.find(function(element) {
  return element > 100;
});

console.log(found); // Output: 130

16. array.findIndex():

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

var array1 = [5, 12, 8, 130, 44];

function isLargeNumber(element) {
  return element > 20;
}

console.log(array1.findIndex(isLargeNumber)); // Output: 3

17. array.includes():

The includes() method determines whether an array includes a certain value among its entries, returning true or false as appropriate.

var array1 = [1, 2, 3];
console.log(array1.includes(2)); // Output: true

var pets = ['cat', 'dog', 'bat'];
console.log(pets.includes('at')); // Output: false

18. array.at():

The at() method (ES2022) takes an integer and returns the item at that index. Negative integers count back from the last item.

const fruits = ['Apple', 'Orange', 'Banana', 'Mango'];
console.log(fruits.at(0));  // Output: Apple
console.log(fruits.at(-1)); // Output: Mango (last element)
console.log(fruits.at(-2)); // Output: Banana

19. array.flat():

The flat() method creates a new array with all sub-array elements concatenated into it recursively up to the specified depth.

const nested = [1, [2, 3], [4, [5, 6]]];
console.log(nested.flat());    // Output: [1, 2, 3, 4, [5, 6]]
console.log(nested.flat(2));   // Output: [1, 2, 3, 4, 5, 6]
console.log(nested.flat(Infinity)); // Output: [1, 2, 3, 4, 5, 6]

20. array.flatMap():

The flatMap() method maps each element using a mapping function, then flattens the result into a new array (one level deep).

const sentences = ['Hello World', 'Foo Bar'];
console.log(sentences.flatMap(s => s.split(' '))); // Output: ['Hello', 'World', 'Foo', 'Bar']

21. array.findLast():

The findLast() method (ES2023) iterates the array in reverse and returns the value of the first element that satisfies the provided testing function.

const numbers = [5, 12, 8, 130, 44];
console.log(numbers.findLast(n => n > 10)); // Output: 44

22. array.findLastIndex():

The findLastIndex() method (ES2023) iterates the array in reverse and returns the index of the first element that satisfies the provided testing function.

const numbers = [5, 12, 8, 130, 44];
console.log(numbers.findLastIndex(n => n > 10)); // Output: 4

Q. What are the benefits of using spread syntax and how is it different from rest syntax?

Spread operator or Spread Syntax allow us to expand the arrays and objects into elements in the case of an array and key-value pairs in the case of an object.

Example:

function sum(x, y, z) {
  return x + y + z;
}

const numbers = [1, 2, 3];
console.log(sum(...numbers));

// ES-5 way
console.log(sum.apply(null, numbers));

Example: Merge arrays

const newBrands = ["Tesla", "Mahindra"];
const brands = ["Ford", "Honda", ...newBrands, "BMW"];

console.log(brands);

Example: Copy array/object

let obj = { a: 10, b: 20, c: 30 };

// spread the object into a list of parameters
let objCopy = { ...obj };

// add new
obj.d = 40;

console.log(JSON.stringify(obj)); // { "a":10, "b":20, "c":30, "d":40 }
console.log(JSON.stringify(objCopy)); // { "a":10, "b":20, "c":30 }

Difference:

The main difference between rest and spread is that the rest operator puts the rest of some specific user-supplied values into a JavaScript array. But the spread syntax expands iterables into individual elements.

Spread SyntaxRest Syntax
Spread operator as its name suggests it spreads or expands the content of the given element.Rest Syntax is just the opposite of spread syntax it collects the data and stores that data in a variable which we can use further in our code.
It expands an Array in form of elements, while in key-value pairs in the case of Objects.It collects the data in the developer's desired format.
You may or may not use the strict mode inside the function containing the spread operator.You can not use the strict mode inside function containing the rest operator.
It will overwrite the identical properties inside two objects and replace the former with the latter.It simply collects all properties and wraps them inside a container.

⚝ Try this example on CodeSandbox

Q. What is the difference between for..in and for..of?

Both for..of and for..in statements iterate over lists; the values iterated on are different though, for..in returns a list of keys on the object being iterated, whereas for..of returns a list of values of the numeric properties of the object being iterated.

  • for in: iterates over all enumerable properties of an object that are keyed by strings.
  • for of: iterates over the values of an iterable objects. including: built-in String, Array, array-like objects (e.g., arguments or NodeList), TypedArray, Map, Set, and user-defined iterables.

Example:

// for..in
const list = [10, 20, 30];

for (let key in list) {
  console.log(key); // "0", "1", "2",
}

// for..of
for (let value of list) {
  console.log(value); // "10", "20", "30"
}

// Works on any iterable: arrays, strings, Map, Set
for (let char of "hello") {
  console.log(char); // "h", "e", "l", "l", "o"
}

⚝ Try this example on CodeSandbox

Q. When to use reduce(), map(), foreach() and filter() in JavaScript?

1. forEach():

It takes a callback function and run that callback function on each element of array one by one. Basically forEach works as a traditional for loop looping over the array and providing array elements to do operations on them.

let numbers = [10, 20, 30];

numbers.forEach(function (number, index) {
  console.log(number + " comes at " + index);
});

// Output
10 comes at 0
20 comes at 1
30 comes at 2

2. filter():

The main difference between forEach() and filter() is that forEach just loop over the array and executes the callback but filter executes the callback and check its return value. If the value is true element remains in the resulting array but if the return value is false the element will be removed for the resulting array.

Note: filter does not update the existing array it will return a new filtered array every time.

let numbers = [10, 20, 30];

let result = numbers.filter(function (number) {
  return number !== 20;
});

console.log(result);

// Output
[10, 30]

3. map():

map() like filter() & forEach() takes a callback and run it against every element on the array but whats makes it unique is it generate a new array based on your existing array.

Like filter(), map() also returns an array. The provided callback to map modifies the array elements and save them into the new array upon completion that array get returned as the mapped array.

let numbers = [10, 20, 30];

let mapped = numbers.map(function (number) {
  return number * 10;
});

console.log(mapped);

// Output
[100, 200, 300]

4. reduce():

reduce() method of the array object is used to reduce the array to one single value.

let numbers = [10, 20, 30];

let sum = numbers.reduce(function (sum, number) {
  return sum + number;
});

console.log(sum); // Output: 60

Key Rules

  • Need a new transformed array? → map()
  • Need a subset? → filter()
  • Need a single output (sum, object, string)? → reduce()
  • Need no return value (side effects)? → forEach()
  • Avoid forEach() when you need a result — use map()/filter()/reduce() instead

⚝ Try this example on CodeSandbox

Q. How do you define JSON arrays?

JSON is an acronym for JavaScript Object Notation, and is "an open standard data interchange format".

JSON array represents ordered list of values. JSON array can store multiple values. It can store string, number, boolean or object in JSON array.

// Empty JSON array
const empty = [ ];


// JSON Array of Numbers
const numbers = [12, 34, 56, 43, 95];


// JSON Array of Objects
{
 "employees": [
   { "name": "Kabir Dixit", "email": "kabir.dixit@gmail.com", "age": 23 },
   { "name": "Mukta Bhagat", "email": "mukta.bhagat@gmail.com", "age": 28 },
   { "name": "Sakshi Ramakrishnan", "email": "sakshi.ramakrishnan@gmail.com", "age": 33 }
  ]
}

// access array values
console.log(employees[0].name) // Kabir Dixit

Q. How to validate JSON Object in javascript?

JSON.parse() function will use string and converts to JSON object and if it parses invalidate JSON data, it throws an exception ( Uncaught SyntaxError: Unexpected string in JSON ).

function isValidJson(json) {
  try {
    JSON.parse(json);
    return true;
  } catch (e) {
    return false;
  }
}

console.log(isValidJson("{}")); // true
console.log(isValidJson("abc")); // false

⚝ Try this example on CodeSandbox

Q. What is the purpose JSON stringify?

When sending data to a web server, the data has to be in a string format. The JSON.stringify() method converts a JavaScript object or value to a JSON string format.

const user = {'name': 'Shashi Meda', 'email': 'shashi.meda@email.com', 'age': 28}

console.log(JSON.stringify(user)); // {"name":"Shashi Meda","email":"shashi.meda@email.com","age":28}

Q. How do you parse JSON string?

When receiving the data from a web server, the data is always in a string format. But you can convert this string value to javascript object using JSON.parse() method.

const user = '{"name": "Shashi Meda", "email": "shashi.meda@email.com", "age": 28}'

console.log(JSON.parse(user));// {'name': 'Shashi Meda', 'email': 'shashi.meda@email.com', 'age': 28}

Q. What is the purpose of compare function while sorting arrays?

The purpose of the compare function is to define an alternative sort order. When the sort() function compares two values, it sends the values to the compare function, and sorts the values according to the returned (negative, zero, positive) value.

If omitted, the array elements are converted to strings, then sorted according to each character's Unicode code point value.

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

numbers.sort((a, b) => b - a);
console.log(numbers); // [5, 4, 3, 2, 1]

⚝ Try this example on CodeSandbox

Q. Can you describe the main difference between a .forEach loop and a .map() loop and why you would pick one versus the other?

To understand the differences between the two, Let us look at what each function does.

1. Array.forEach():

  • Iterates through the elements in an array.
  • Executes a callback for each element.
  • Does not return a value.
const numbers = [10, 20, 30];
const doubled = numbers.forEach((num, index) => {
  return num * 2;
});

console.log(doubled) // undefined

2. Array.map():

  • Iterates through the elements in an array.
  • "Maps" each element to a new element by calling the function on each element, creating a new array as a result.
const numbers = [10, 20, 30];
const doubled = numbers.map(num => {
  return num * 2;
});

console.log(doubled) // [20, 40, 60]

The main difference between .forEach and .map() is that .map() returns a new array. If you need the result, but do not wish to mutate the original array, .map() is the clear choice. If you simply need to iterate over an array, forEach is a fine choice.

⚝ Try this example on CodeSandbox

Q. What is unshift() method in JavaScript?

The unshift() method adds one or more elements to the beginning of an array and returns the new length of the array.

Example:

const numbers = [10, 20, 30];

console.log(numbers.unshift(40, 50)); // 5
console.log(numbers); // [40, 50, 10, 20, 30]

⚝ Try this example on CodeSandbox

Q. What is a rest parameter?

The rest parameter is used to represent an indefinite number of arguments as an array. The important point here is only the function's last parameter can be a "rest parameter".

This feature has been introduced to reduce the boilerplate code that was induced by the arguments.

Example:

function sum(...args) {
  return args.reduce((previous, current) => {
    return previous + current;
  });
}

console.log(sum(10)); // 10
console.log(sum(10, 20)); // 30
console.log(sum(10, 20, 30)); // 60

⚝ Try this example on CodeSandbox

Q. What happens if you do not use rest parameter as a last argument?

The rest parameter should be the last argument, as its job is to collect all the remaining arguments into an array.

Example: If you define a function like below it does not make any sense and will throw an SyntaxError.

function display(a, ...args, b) {
  console.log(a);
  for (let i = 0; i < args.length; i++) {
    console.log(args[i]);
  }
  console.log(b);
}

display(10, 20, 30, 40, 50);

// Output
SyntaxError: Rest element must be last element

⚝ Try this example on CodeSandbox

Q. What is difference between [] and new Array()?

[] and new Array() are two different ways of creating an array, but they are functionally equivalent.

The primary difference between them is in how they are created and in their behavior when used with certain methods.

[] is a shorthand for creating a new array. It is the preferred way to create an array in most cases, because it's more concise and easier to read. For example:

const myArray = []; // create a new empty array

On the other hand, new Array() is a constructor function that creates a new array object. It can be used to create an array of a specific length or with specific elements. For example:

const myArray = new Array(); // create a new empty array
const myOtherArray = new Array(3); // create a new array with a length of 3
const myThirdArray = new Array("a", "b", "c"); // create a new array with three elements

One potential pitfall of using new Array() is that it can be ambiguous when you pass a single argument to the constructor. For example, new Array(3) creates an array with a length of 3, but new Array("3") creates an array with a single element, the string "3". This is because the argument is treated as the value of the first element when it's a non-negative integer, but as the length of the array when it's a string or a negative integer.

In summary, [] is the preferred way to create a new array in JavaScript, while new Array() is an alternative way that can be used when you need more control over the array's length or contents.

Q. What are Array.from(), Array.of() and Array.isArray() methods?

1. Array.from():

Array.from() creates a new, shallow-copied array from an array-like or iterable object such as a Set, Map, NodeList, or string.

// From a string
console.log(Array.from('hello')); // ['h', 'e', 'l', 'l', 'o']

// From a Set
const set = new Set([1, 2, 3, 2, 1]);
console.log(Array.from(set)); // [1, 2, 3]

// With a map function
console.log(Array.from([1, 2, 3], x => x * 2)); // [2, 4, 6]

// From array-like object
console.log(Array.from({ length: 3 }, (_, i) => i + 1)); // [1, 2, 3]

2. Array.of():

Array.of() creates a new array from a variable number of arguments, regardless of number or type. It fixes the ambiguity of new Array() with a single numeric argument.

console.log(Array.of(7));       // [7]         (one element)
console.log(new Array(7));      // [ , , , , , , ] (empty array of length 7!)

console.log(Array.of(1, 2, 3)); // [1, 2, 3]

3. Array.isArray():

Array.isArray() returns true if the passed value is an array, false otherwise. It is more reliable than instanceof when working across different frames or windows.

console.log(Array.isArray([1, 2, 3]));  // true
console.log(Array.isArray('hello'));     // false
console.log(Array.isArray({ length: 3 })); // false
console.log(Array.isArray(new Array())); // true

# 8. CONTROL FLOW


Q. What are the different types of control flow statements available in JavaScript?

Control flow determines the order in which statements are executed. JavaScript provides three main categories:

CategoryStatements
Conditionalif, else if, else, switch, ternary (? :)
Looping / Iterationfor, while, do...while, for...in, for...of
Jump / Transferbreak, continue, return, throw

By default, JavaScript executes code top-to-bottom (sequential flow). Control flow statements allow branching and repetition, enabling complex logic.

Example:

// Conditional branching
const score = 78;

if (score >= 90) {
  console.log("Grade: A");
} else if (score >= 75) {
  console.log("Grade: B"); // ← executed
} else {
  console.log("Grade: C");
}

// Looping
const students = ["Alice", "Bob", "Carol"];
for (let i = 0; i < students.length; i++) {
  console.log(students[i]);
}

// Jump: exit loop early
for (let i = 0; i < 10; i++) {
  if (i === 5) break;
  console.log(i); // 0, 1, 2, 3, 4
}

Q. How does the if...else if...else ladder work, and what are its performance considerations?

The if...else if...else ladder evaluates conditions sequentially from top to bottom and executes the first block whose condition is true. Once a matching branch executes, all remaining else if and else blocks are skipped — making it short-circuit by design.

Performance consideration: JavaScript evaluates each condition until one is truthy. Placing the most frequently matched condition first reduces the number of comparisons at runtime.

Example:

function classify(temperature) {
  if (temperature < 0) {
    return "Freezing";
  } else if (temperature < 10) {
    return "Cold";
  } else if (temperature < 20) {
    return "Mild";
  } else if (temperature < 30) {
    return "Warm";
  } else {
    return "Hot";
  }
}

console.log(classify(-5));  // Freezing
console.log(classify(15));  // Mild
console.log(classify(35));  // Hot

Falsy vs. Truthy awareness:

// These values are falsy in JavaScript:
// false, 0, "", null, undefined, NaN

const value = 0;

if (value) {
  console.log("Truthy");
} else {
  console.log("Falsy"); // ← executed because 0 is falsy
}

Q. When should you use a switch statement instead of if...else if, and what are its pitfalls?

A switch statement compares a single expression against multiple discrete values using strict equality (===). It is preferable over if...else if when:

  • You are comparing one variable against many possible literal values.
  • Readability is a priority (avoids repetitive condition checks against the same variable).

Pitfall — Fall-through: Without a break statement, execution "falls through" to the next case, which is a common source of bugs.

Example:

const day = "Monday";

switch (day) {
  case "Monday":
  case "Tuesday":
  case "Wednesday":
  case "Thursday":
  case "Friday":
    console.log("Weekday");
    break;
  case "Saturday":
  case "Sunday":
    console.log("Weekend");
    break;
  default:
    console.log("Unknown day");
}
// Output: Weekday

Intentional fall-through example:

// Grouping cases that share behavior — intentional fall-through
const statusCode = 404;

switch (statusCode) {
  case 400:
  case 401:
  case 403:
  case 404:
    console.log("Client error");
    break;
  case 500:
  case 502:
  case 503:
    console.log("Server error");
    break;
  default:
    console.log("Unknown status");
}
// Output: Client error

Pitfall — switch uses ===, not ==:

const x = "1";
switch (x) {
  case 1:
    console.log("Number 1"); // NOT matched
    break;
  case "1":
    console.log("String '1'"); // ← matched
    break;
}

Q. Explain the difference between for, for...in, and for...of loops with practical examples.

LoopIterates OverUse Case
forIndex-based counterArrays, precise iteration control
for...inEnumerable property keysObject properties (avoid for arrays)
for...ofIterable values (arrays, strings, Maps, Sets, generators)Arrays, strings, and other iterables

Example:

// --- for loop: classic index-based ---
const arr = [10, 20, 30];

for (let i = 0; i < arr.length; i++) {
  console.log(`Index ${i}: ${arr[i]}`);
}
// Index 0: 10 | Index 1: 20 | Index 2: 30

// --- for...in: object property keys ---
const user = { name: "Alice", age: 30, role: "admin" };

for (const key in user) {
  console.log(`${key}: ${user[key]}`);
}
// name: Alice | age: 30 | role: admin

// --- for...of: iterable values ---
const fruits = ["apple", "banana", "cherry"];

for (const fruit of fruits) {
  console.log(fruit);
}
// apple | banana | cherry

// for...of also works on strings
for (const char of "hello") {
  process.stdout.write(char + " ");
}
// h e l l o

// for...of with Map
const scores = new Map([["Alice", 95], ["Bob", 87]]);
for (const [name, score] of scores) {
  console.log(`${name}: ${score}`);
}

Why avoid for...in on arrays?

const arr = [1, 2, 3];
arr.custom = "oops"; // accidentally adding a property

for (const key in arr) {
  console.log(key); // "0", "1", "2", "custom" ← includes inherited/custom keys!
}

for (const val of arr) {
  console.log(val); // 1, 2, 3 ← only values, safe
}

Real-World Use Case:

In a Node.js data-processing pipeline, for...of with await (inside an async function) iterates over a paginated API result set sequentially — something forEach cannot do since it ignores returned Promises. for...in is used to dynamically enumerate config object keys when serializing environment-specific settings.

Q. How do while and do...while loops differ, and when should each be used?

LoopCondition CheckMinimum ExecutionsUse When
whileBefore each iteration0 (may never run)Number of iterations is unknown; condition may be false initially
do...whileAfter each iteration1 (always runs once)Body must execute at least once regardless of condition

Example:

// --- while loop ---
let stock = 5;

while (stock > 0) {
  console.log(`Items remaining: ${stock}`);
  stock--;
}
// Runs 5 times. If stock were 0 initially, it would never run.

// --- do...while loop ---
let attempts = 0;
let pin;

do {
  pin = prompt("Enter 4-digit PIN:"); // Must show prompt at least once
  attempts++;
} while (pin !== "1234" && attempts < 3);

console.log(attempts === 3 ? "Locked out" : "Access granted");

Infinite loop guard — always ensure the condition can become false:

// DANGER: this never terminates
// while (true) { }

// SAFE: controlled exit with break
let counter = 0;
while (true) {
  counter++;
  if (counter >= 5) break;
}
console.log(counter); // 5

Q. What is the purpose of break and continue statements, and how do they interact with nested loops?

  • break: Immediately terminates the innermost loop or switch statement and transfers control to the statement following it.
  • continue: Skips the current iteration of the innermost loop and jumps to the next iteration's condition check.

Example:

// --- break: stop on first match ---
const inventory = [
  { id: 1, name: "Widget A", inStock: false },
  { id: 2, name: "Widget B", inStock: true },
  { id: 3, name: "Widget C", inStock: true },
];

let firstAvailable = null;
for (const item of inventory) {
  if (item.inStock) {
    firstAvailable = item;
    break; // stop searching once found
  }
}
console.log(firstAvailable); // { id: 2, name: "Widget B", inStock: true }

// --- continue: skip unwanted items ---
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const evens = [];

for (const n of numbers) {
  if (n % 2 !== 0) continue; // skip odd numbers
  evens.push(n);
}
console.log(evens); // [2, 4, 6, 8, 10]

Labeled statements — breaking outer loops:

// Without labels, break only exits the inner loop
outerLoop: for (let i = 0; i < 3; i++) {
  for (let j = 0; j < 3; j++) {
    if (i === 1 && j === 1) {
      break outerLoop; // exits BOTH loops
    }
    console.log(`i=${i}, j=${j}`);
  }
}
// i=0,j=0 | i=0,j=1 | i=0,j=2 | i=1,j=0  ← stops here

Real-World Use Case:

In a search autocomplete engine, break short-circuits a loop over a large dataset once the desired number of suggestions is collected, preventing unnecessary iterations. continue is used in data-cleaning pipelines to skip null or malformed records without nesting the entire logic in an extra if block — keeping cyclomatic complexity low and the code easier to review.

Q. What is the ternary operator and how does it compare to if...else for conditional expressions?

The ternary operator (condition ? expressionIfTrue : expressionIfFalse) is JavaScript's only three-operand operator. It evaluates a condition and returns one of two expressions. Unlike if...else (a statement), the ternary is an expression — it produces a value and can be used inline.

Example:

// if...else statement — cannot be used inline
let label;
const isLoggedIn = true;

if (isLoggedIn) {
  label = "Logout";
} else {
  label = "Login";
}

// Ternary expression — equivalent, concise, assignable
const label2 = isLoggedIn ? "Logout" : "Login";

console.log(label2); // "Logout"

// Inline in JSX (React)
// <button>{isLoggedIn ? "Logout" : "Login"}</button>

// Nested ternary — use sparingly, prefer if...else for readability
const score = 72;
const grade = score >= 90 ? "A"
            : score >= 75 ? "B"
            : score >= 60 ? "C"
            : "F";

console.log(grade); // "C"

When NOT to use ternary:

// Bad: using ternary for side effects reduces readability
isLoggedIn ? doLogout() : doLogin();

// Better: use if...else for side effects
if (isLoggedIn) {
  doLogout();
} else {
  doLogin();
}

Real-World Use Case:

In React, the ternary operator is the standard pattern for conditional rendering within JSX since JSX is an expression context — if statements cannot appear inside {} interpolation. For example, rendering a loading spinner vs. a data table based on isLoading state is universally handled with a ternary.

Q. What are the nullish coalescing (??) and optional chaining (?.) operators, and how do they relate to control flow?

Both operators introduced in ES2020 reduce verbose defensive if checks for null/undefined values:

  • Nullish coalescing (??): Returns the right-hand operand when the left is null or undefined (not other falsy values like 0 or "").
  • Optional chaining (?.): Short-circuits and returns undefined instead of throwing a TypeError when accessing a property on null or undefined.

Example:

// --- Nullish Coalescing ?? ---
const userAge = 0; // falsy but valid!

// Old approach (bug: treats 0 as falsy)
const age1 = userAge || 18; // 18  ← WRONG: overwrites valid 0
// Correct approach
const age2 = userAge ?? 18; // 0   ← CORRECT: 0 is not null/undefined

console.log(age1, age2); // 18, 0

// --- Optional Chaining ?. ---
const user = {
  profile: {
    address: null
  }
};

// Without optional chaining — throws TypeError
// console.log(user.profile.address.city); // TypeError!

// With optional chaining — safe
console.log(user.profile?.address?.city); // undefined (no error)

// Works on methods too
const result = user.getPermissions?.(); // undefined if method doesn\'t exist

// Works on arrays
const first = user.tags?.[0]; // undefined if tags is null/undefined

// --- Combined usage ---
const city = user.profile?.address?.city ?? "Unknown City";
console.log(city); // "Unknown City"

Real-World Use Case:

When consuming third-party REST APIs or GraphQL responses, deeply nested fields may be absent depending on the query. Optional chaining eliminates dozens of guard clauses like if (response && response.data && response.data.user). Nullish coalescing is critical when a user-configurable setting has 0 or "" as a valid value — falling back with || would incorrectly override those legitimate settings.

Q. What are labeled statements in JavaScript and when are they appropriate?

A labeled statement is an identifier followed by a colon (:) placed before a loop or block. Labels can be referenced by break and continue to control the flow of outer loops from within a nested loop. They are rarely used — but in specific nested-iteration scenarios they can be clearer than managing manual flags.

Example:

// --- Using label with break to exit a matrix search ---
function findValue(matrix, target) {
  let found = false;

  search: for (let row = 0; row < matrix.length; row++) {
    for (let col = 0; col < matrix[row].length; col++) {
      if (matrix[row][col] === target) {
        console.log(`Found at [${row}][${col}]`);
        found = true;
        break search; // exit both loops immediately
      }
    }
  }

  return found;
}

const matrix = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9],
];

findValue(matrix, 5); // Found at [1][1]

// --- Using label with continue ---
outer: for (let i = 0; i < 3; i++) {
  for (let j = 0; j < 3; j++) {
    if (j === 1) continue outer; // skip rest of inner loop, go to next outer iteration
    console.log(`i=${i}, j=${j}`);
  }
}
// i=0,j=0 | i=1,j=0 | i=2,j=0

Alternative to a flag variable:

// Without labels — requires a flag
let found = false;
for (let i = 0; i < rows && !found; i++) {
  for (let j = 0; j < cols; j++) {
    if (grid[i][j] === target) { found = true; break; }
  }
}

// With labels — no flag needed, intent is clearer
searchGrid: for (let i = 0; i < rows; i++) {
  for (let j = 0; j < cols; j++) {
    if (grid[i][j] === target) break searchGrid;
  }
}

Real-World Use Case:

In a pathfinding algorithm (e.g., BFS/DFS on a 2D game map) or a seat-selection engine scanning rows and seats in a stadium booking system, a labeled break exits all nested loops the moment a valid seat is located — avoiding a boolean flag variable that would otherwise pollute the outer scope and add an extra condition to every loop header.

Q. What is short-circuit evaluation in JavaScript and how does it control execution flow?

JavaScript's logical operators (&&, ||, ??) use short-circuit evaluation: the right-hand operand is only evaluated if necessary. This is not just a performance optimization — it is a widely used control flow mechanism.

OperatorShort-circuits when left side isReturns
&& (AND)falsyLeft operand (skips right)
|| (OR)truthyLeft operand (skips right)
?? (Nullish)not null/undefinedLeft operand (skips right)

Example:

// --- && short-circuit: right side only runs if left is truthy ---
const user = { isAdmin: true };

user.isAdmin && console.log("Admin panel loaded"); // ← runs
null && console.log("This never runs");             // ← skipped

// --- || short-circuit: right side only runs if left is falsy ---
const config = null;
const settings = config || { theme: "dark", lang: "en" }; // default fallback
console.log(settings); // { theme: "dark", lang: "en" }

// --- Practical: guard before calling a function ---
function logError(err) {
  err && err.message && console.error(err.message);
}

logError(null);            // nothing happens (safe)
logError({ message: "Failed" }); // "Failed"

// --- Chaining for conditional execution (React pattern) ---
const isLoading = false;
const data = [1, 2, 3];

// Equivalent to: if (!isLoading && data.length > 0) render()
const output = !isLoading && data.length > 0 && "Render data";
console.log(output); // "Render data"

Gotcha — using && for side effects:

// This is valid but can be confusing:
isAuthenticated && fetchDashboard();

// Prefer explicit if for clarity in complex flows:
if (isAuthenticated) {
  fetchDashboard();
}

Real-World Use Case:

In React, && short-circuit is the idiomatic pattern for conditional rendering: {isLoggedIn && <UserMenu />} renders the component only when isLoggedIn is truthy, with no JSX if block needed. The || operator is universally used for default parameter fallbacks before optional chaining became standard. In Node.js middleware chains, && guards ensure a dependency (e.g., a database connection) is available before proceeding to query logic.

# 9. FUNCTIONS


Q. What are the benefits of using arrow function over es5 function?

Arrows is a new syntax for functions, which brings several benefits:

  • Arrow syntax automatically binds this to the surrounding code's context
  • The syntax allows an implicit return when there is no body block, resulting in shorter and simpler code in some cases
  • Last but not least, => is shorter and simpler than function, although stylistic issues are often subjective

Example 01: Arrow Function with No Argument

If a function doesn't take any argument, then you should use empty parentheses.

let greet = () => console.log('Hello');
greet(); // Hello

Example 02: Arrow Function with One Argument

If a function has only one argument, you can omit the parentheses.

let greet = x => console.log(x);
greet('Hello'); // Hello 

Example 03: Arrow Function as an Expression

You can also dynamically create a function and use it as an expression.

let age = 25;

let welcome = (age < 18) ?
  () => console.log('Baby') :
  () => console.log('Adult');

welcome(); // Adult

Example 04: Multiline Arrow Functions

If a function body has multiple statements, you need to put them inside curly brackets {}.

let area = (r) => {
  const pi = 3.14;
  return pi * r * r;
}

let result = area(10);
console.log(result); // 314

Note: Unlike regular functions, arrow functions do not have their own this. The value of this inside an arrow function remains the same throughout the lifecycle of the function and is always bound to the value of this in the closest non-arrow parent function.

⚝ Try this example on CodeSandbox

Q. What is the benefit of using the arrow syntax for a method in a constructor?

The main advantage of using an arrow function as a method inside a constructor is that the value of this gets set at the time of the function creation and can't change after that. So, when the constructor is used to create a new object, this will always refer to that object.

Example:

const Person = function(firstName) {
  this.firstName = firstName;
  this.sayName1 = function() { console.log(this.firstName); }; // regular function
  this.sayName2 = () => { console.log(this.firstName); };      // arrow function
};

const john = new Person('John');
const dave = new Person('Dave');

john.sayName1(); // John
john.sayName2(); // John

// Regular function: 'this' can be changed with call/apply/bind
john.sayName1.call(dave); // Dave
john.sayName2.call(dave); // John — arrow function ignores call/apply/bind

// Detached from object: regular function loses 'this'
const fn1 = john.sayName1;
fn1(); // undefined

const fn2 = john.sayName2;
fn2(); // John — arrow function retains original 'this'

Q. Difference between Function, Method and Constructor calls in JavaScript?

1. Functions: The simplest usages of function call:

function display(name) {
  return "Hello " + name;
}

display("World"); // "Hello World"

2. Methods: in JavaScript are nothing more than object properties that are functions.

var obj = {
  display : function() {
    return "Hello " + this.name;
  },
  name: 'Minali Peri'
}
obj.display();  // "Hello Minali Peri"

3. Constructors: Like function and method, constructors are defined with function.

function Employee(name, age) {
  this.name = name;
  this.age = age;
}

var emp1 = new Employee('Drishya Sama', 28);
emp1.name; // "Drishya Sama"
emp1.age; // 28

Unlike function calls and method calls, a constructor call new Employee('Drishya Sama', 28) creates a new object and passes it as the value of this, and implicitly returns the new object as its result. The primary role of the constructor function is to initialize the object.

Q. When you should not use arrow functions in ES6?

An arrow function is a shorter syntax for a function expression and does not have its own this, arguments, super, or new.target. These function are best suited for non-method functions, and they cannot be used as constructors.

Arrow functions in ES6 has two limitations:

  • Do not work with new
  • Fixed this bound to scope at initialisation

When should not use Arrow Functions:

1. Object methods:

The counter object has two methods: current() and next(). The current() method returns the current counter value and the next() method returns the next counter value.

// Usin arrow function
const counter = {
  count: 0,
  next: () => ++this.count,
  current: () => this.count
};

console.log(counter.next()); // NaN

2. Event handlers:

If we click the button, we would get a TypeError. It is because this is not bound to the button, but instead bound to its parent scope.

let button = document.getElementById('press');

button.addEventListener('click', () => {
  this.classList.toggle('on');
});

3. Prototype methods:

The this value in these next() and current() methods reference the global object. Since the this value inside the methods to reference the Counter object, it needs to use the regular functions instead

function Counter() {
    this.count = 0;
}

Counter.prototype.next = () => {
    return this.count;
};

Counter.prototype.current = () => {
    return ++this.next;
}

4. Functions that use the arguments object:

Arrow functions don't have the arguments object. Therefore, if a function that uses arguments object, you cannot use the arrow function.

const concat = (separator) => {
    let args = Array.prototype.slice.call(arguments, 1);
    return args.join(separator);
}

⚝ Try this example on CodeSandbox

Q. What are the properties of function objects in javascript?

JavaScript function objects are used to define a piece of JavaScript code. This code can be called within a JavaScript code as and when required.

Javascript Function Objects Property:

NameDescription
argumentsAn array corresponding to the arguments passed to a function.
arguments.calleeRefers the currently executing function.
arguments.lengthRefers the number of arguments defined for a function.
constructorSpecifies the function that creates an object.
lengthThe number of arguments defined by the function.
prototypeAllows adding properties to a Function object.

Q. What is a first class function?

In javaScript, functions can be stored as a variable inside an object or an array as well as it can be passed as an argument or be returned by another function. That makes function first-class function in JavaScript.

A first-class function means functions are treated as values — they can be:

  • Assigned to variables: const greet = function() { return "Hello"; }
  • Passed as arguments: arr.map(fn)
  • Returned from other functions: function outer() { return function inner() {} }
  • Stored in data structures: const obj = { fn: () => {} }

JavaScript fully supports first-class functions, which enables patterns like callbacks, higher-order functions, and closures.

Example 01: Assign a function to a variable

const message = function() {
   console.log("Hello World!");
}

message(); // Invoke it using the variable

Example 02: Pass a function as an Argument

function sayHello() {
   return "Hello, ";
}
function greeting(helloMessage, name) {
  console.log(helloMessage() + name);
}
// Pass `sayHello` as an argument to `greeting` function
greeting(sayHello, "JavaScript!");

Example 03: Return a function

function sayHello() {
   return function() {
      console.log("Hello!");
   }
}

Example 04: Using a variable

const sayHello = function() {
   return function() {
      console.log("Hello!");
   }
}
const myFunc = sayHello();
myFunc();

Example 05: Using double parentheses

function sayHello() {
   return function() {
      console.log("Hello!");
   }
}
sayHello()();

We are using double parentheses ()() to invoke the returned function as well.

⚝ Try this example on CodeSandbox

Q. What is a higher order function?

A Higher-Order function is a function that receives a function as an argument or returns the function as output.

For example, Array.prototype.map(), Array.prototype.filter() , Array.prototype.forEach() and Array.prototype.reduce() are some of the Higher-Order functions in javascript.

Example 01: .map()

const array = [10, 20, 30];

const result = array.map(function (item) {
  return item * 2;
});
console.log(result); // [20, 40, 60]

Example 02: .filter()

const randomNumbers = [4, 11, 42, 14, 39];

const filteredArray = randomNumbers.filter((number) => {
  return number > 15;
});
console.log(filteredArray); // [42, 39]

Example 03: .forEach()

const numbers = [28, 77, 45];

numbers.forEach((number) => {
  console.log(number);
});

Example 04: .reduce()

const arrayOfNumbers = [10, 20, 30];

const sum = arrayOfNumbers.reduce((accumulator, currentValue) => {
  return accumulator + currentValue;
});
console.log("Sum: " + sum); // 60

⚝ Try this example on CodeSandbox

Q. What is a unary function?

Unary function (i.e. monadic) is a function that accepts exactly one argument. It stands for single argument accepted by a function.

// Unary function
const unaryFunction = (number) => number + 10;

console.log(unaryFunction(10)); // 20

⚝ Try this example on CodeSandbox

Q. What is currying function?

Currying is the process of taking a function with multiple arguments and turning it into a sequence of functions each with only a single argument.

In other words, when a function, instead of taking all arguments at one time, takes the first one and return a new function that takes the second one and returns a new function which takes the third one, and so forth, until all arguments have been fulfilled.

// Normal function
const add = (a, b, c) => {
  return a + b + c;
};
console.log(add(10, 10, 10)); // 30

// Currying function
const addCurry = (a) => {
  return (b) => {
    return (c) => {
      return a + b + c;
    };
  };
};
console.log(addCurry(20)(20)(20)); // 60

Note: Curried functions are great to improve code re-usability and functional composition.

⚝ Try this example on CodeSandbox

Q. What is a pure function?

Pure functions are functions that accept an input and returns a value without modifying any data outside its scope(Side Effects). Its output or return value must depend on the input/arguments and pure functions must return a value.

Example: Pure Function

It is a pure function because you always get a Hello <name> as output for the <name> pass as an input.

// Pure Function

function sayGreeting(name) {
  return `Hello ${name}`;
}

console.log(sayGreeting("World"));

Example: Not Pure Function

The function's output now depends on an outer state called greeting. What if someone changes the value of the greeting variable to Hola? It will change the output of the sayGreeting() function even when you pass the same input.

let greeting = "Hello";

function sayGreeting(name) {
  return `${greeting} ${name}`;
}

A function must pass two tests to be considered pure:

  • Same inputs always return same outputs
  • No side-effects

Benefits:

  • Predictable: It produces a predictable output for the same inputs.
  • Readable: Anyone reading the function as a standalone unit can understand its purpose completely.
  • Reusable: Can reuse the function at multiple places of the source code without altering its and the caller's behavior.
  • Testable: We can test it as an independent unit.

Q. What is memoization in JavaScript?

Memoization is a programming technique which attempts to increase a function's performance by caching its previously computed results.

Each time a memoized function is called, its parameters are used to index the cache. If the data is present, then it can be returned, without executing the entire function. Otherwise the function is executed and then the result is added to the cache.

// Memoized function to Add Number

const memoizedAdd = () => {
  let cache = {};
  return (number) => {
    if (number in cache) {
      console.log('Fetching from cache: ');
      return cache[number];
    }
    else {
      console.log('Calculating result: ');
      let result = number + 10;
      cache[number] = result;
      return result;
    }
  }
}
// returned function from memoizedAdd
const sum = memoizedAdd();

console.log(sum(10)); // Calculating result: 20
console.log(sum(10)); // Fetching from cache: 20

⚝ Try this example on CodeSandbox

Q. What is an arguments object?

The arguments object is an Array-like object ( arguments ) accessible inside functions that contains the values of the arguments passed to that function.

Example:

/**
 * Arguments Object
 */

function sum() {
  let total = 0;
  for (let i = 0, len = arguments.length; i < len; ++i) {
    total += arguments[i];
  }
  return total;
}

sum(10, 20, 30); // returns 60

⚝ Try this example on CodeSandbox

Q. What is the way to find the number of parameters expected by a function?

The length property indicates the number of parameters expected by the function.

// function.length

function fun1() {}
console.log(fun1.length); // 0

function fun2(arg1, arg2) {}
console.log(fun2.length); // 2

⚝ Try this example on CodeSandbox

Q. What is bind method in javascript?

The bind() method creates a new function, when invoked, has the this sets to a provided value. The bind() method allows an object to borrow a method from another object without making a copy of that method. This is known as function borrowing in JavaScript.

Example:

/**
 * bind() function
 */
const person = {
  firstName: "Chhavi",
  lastName: "Goswami",
  fullName: function () {
    return this.firstName + " " + this.lastName;
  }
};

const member = {
  firstName: "Vasuda",
  lastName: "Sahota"
};

let fullName = person.fullName.bind(member);
console.log(fullName()); // Vasuda Sahota

⚝ Try this example on CodeSandbox

Q. What is an anonymous function?

An anonymous function is a function without a name. Anonymous functions are commonly assigned to a variable name or used as a callback function.

Example 01: Anonymous function

let show = function () {
  console.log("Anonymous function");
};
show();

Example 02: anonymous functions as arguments

setTimeout(function () {
  console.log("Execute later after 1 second");
}, 1000);

Example 03: Immediately invoked function execution

const person = {
  firstName: "Ayaan",
  lastName: "Memon"
};

(function () {
  console.log(person.firstName + " " + person.lastName); // Ayaan Memon
})(person);

Example 04: Arrow functions

let add = (a, b) => a + b;

add(10, 20); // 30

⚝ Try this example on CodeSandbox

Q. Explain the difference between function foo() {} and var foo = function() {}?

1. Function Declaration:

Function declarations are evaluated upon entry into the enclosing scope, before any step-by-step code is executed. The function's name (foo) is added to the enclosing scope.

foo(); // Function Declaration Example!

function foo() {
  console.log("Function Declaration Example!");
}

2. Function Expression:

Function expressions are evaluated as part of the step-by-step code, at the point where they appear. That one creates a function with no name, which it assigns to the foo variable.

foo(); // TypeError: foo is not a function

var foo = function() {
  console.log(typeof foo); // function
};
console.log(typeof foo);     // undefined

⚝ Try this example on CodeSandbox

Q. When to use function declarations and expressions in JavaScript?

Function Declarations:

A declared function is "saved for later use", and will be executed later, when it is invoked (called).

// Function declaration
function add(num1, num2) {
	return num1 + num2;
}

function is only declared here. For using it, it must be invoked using function name. e.g add(10, 20);

Function Expression:

A function expression can be stored in a variable:

// Function expression
var add = function (num1, num2) {
	return num1 + num2;
};

After a function expression has been stored in a variable, the variable can be used as a function. Functions stored in variables do not need function names. They are always invoked (called) using the variable name.

Difference:

  • Function declarations load before any code is executed while Function expressions load only when the interpreter reaches that line of code.
  • Similar to the var statement, function declarations are hoisted to the top of other code. Function expressions aren't hoisted, which allows them to retain a copy of the local variables from the scope where they were defined.

Benefits of Function Expressions:

There are several different ways that function expressions become more useful than function declarations.

  • As closures
  • As arguments to other functions
  • As Immediately Invoked Function Expressions (IIFE)

Q. What is the difference between a method and a function in javascript?

1. Function:

A function is a piece of code that is called by name and function itself not associated with any object and not defined inside any object. It can be passed data to operate on (i.e. parameter) and can optionally return value.

Example:

// Function 
function message(msg) {
  return msg;
}

// Call the function
message("Welcome to JavaScript");

Here, message() function call is not associated with object hence not invoked through any object.

2. Method:

A JavaScript method is a property of an object that contains a function definition. Methods are functions stored as object properties.

Example:

// Method
let employee = {
  firstName: "Ajay",
  lastName: "Nagi",
  getName: function () {
    return "Employe Name: " + this.firstName + " " + this.lastName;
  }
};

// Call the method
console.log(employee.getName());

Here employee is an object and getName is a method which is associated with employee.

⚝ Try this example on CodeSandbox

Q. What is Function binding?

Function binding ( .bind() ) is a method on the prototype of all functions in JavaScript. It allows to create a new function from an existing function, change the new function's this context, and provide any arguments you want the new function to be called with. The arguments provided to bind will precede any arguments that are passed to the new function when it is called.

Example:

// Function Binding
const person = {
  firstName: "Nirupama",
  lastName: "Randhawa",
  getName: function () {
    return this.firstName + " " + this.lastName;
  }
};

const member = {
  firstName: "Alisha",
  lastName: "Chhabra"
};

let getName = person.getName.bind(member);
console.log(getName()); // Alisha Chhabra

⚝ Try this example on CodeSandbox

Q. Explain how this works in JavaScript?

The this keyword refers to an object. Which object depends on how this is being invoked (used or called). The this keyword refers to different objects depending on how it is used.

  • In an object method, this refers to the object.
  • Alone, this refers to the global object.
  • In a function, this refers to the global object.
  • In a function, in strict mode, this is undefined.
  • In an event, this refers to the element that received the event.
  • Methods like call(), apply(), and bind() can refer this to any object.

Example:

// this keyword in object method

const person = {
  firstName: "Nirupama",
  lastName: "Randhawa",
  getName: function () {
    return this.firstName + " " + this.lastName;
  }
};

Q. What is generator in JS?

Generator-Function:

A generator-function is defined like a normal function, but whenever it needs to generate a value, it does so with the yield keyword rather than return. The yield statement suspends function's execution and sends a value back to caller, but retains enough state to enable function to resume where it is left off. When resumed, the function continues execution immediately after the last yield run.

Syntax:

function* gen() {
     yield 1;
     yield 2;
     yield 3;
     ...
}

Generator-Object:

Generator functions return a generator object. Generator objects are used either by calling the next method on the generator object or using the generator object in a "for in" loop.

Example:

// Generate Function

function* fun() {
  yield 10;
  yield 20;
  yield 30;
}

// Calling the Generate Function
var gen = fun(); // returns a Generator object (doesn\'t run yet)
gen.next(); // { value: 10, done: false }
gen.next(); // { value: 20, done: false }
gen.next(); // { value: 30, done: false }
gen.next(); // { value: undefined, done: true }

⚝ Try this example on CodeSandbox

Q. Compare Async-Await and Generators usage to achive same functionality?

1. Generators/Yield:

Generators are objects created by generator functions — functions with an * (asterisk) next to their name. The yield keyword pauses generator function execution and the value of the expression following the yield keyword is returned to the generator's caller. It can be thought of as a generator-based version of the return keyword.

Example:

// Generator function

const generator = (function* () {
  // waiting for .next()
  const number = yield 5;
  // waiting for .next()
  console.log(number); // => 15
})();

console.log(generator.next()); // => { done: false, value: 5 }
console.log(generator.next(15)); // => { done: true, value: undefined }

2. Async/Await:

Async keyword is used to define an asynchronous function, which returns a AsyncFunction object.

Await keyword is used to pause async function execution until a Promise is fulfilled, that is resolved or rejected, and to resume execution of the async function after fulfillments. When resumed, the value of the await expression is that of the fulfilled Promise.

Key points:

  1. Await can only be used inside an async function.
  2. Functions with the async keyword will always return a promise.
  3. Multiple awaits will always run in sequential order under a same function.
  4. If a promise resolves normally, then await promisereturns the result. But in case of a rejection it throws the error, just if there were a throw statement at that line.
  5. Async function cannot wait for multiple promises at the same time.
  6. Performance issues can occur if using await after await as many times one statement doesn't depend on the previous one.

Example:

// Async/Await

async function asyncFunction() {
  const promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("resolved!"), 1000);
  });

  const result = await promise;
  // wait till the promise resolves (*)

  console.log(result); // "resolved!"
}

asyncFunction();

Generator and Async-await — Comparison:

  1. Generator functions/yield and Async functions/await can both be used to write asynchronous code that "waits", which means code that looks as if it was synchronous, even though it really is asynchronous.
  2. Generator function are executed yield by yield i.e one yield-expression at a time by its iterator (the next method) where as Async-await, they are executed sequential await by await.
  3. Async/await makes it easier to implement a particular use case of Generators.
  4. The return value of Generator is always {value: X, done: Boolean} where as for Async function it will always be a promise that will either resolve to the value X or throw an error.
  5. Async function can be decomposed into Generator and promise implementation which are good to know stuff.

⚝ Try this example on CodeSandbox

Q. How do you compare two date objects?

Two dates can be compared by converting them into numeric values using date.getTime() method to correspond to their time. Also, the relational operators <, <=, >, >= can be used to compare JavaScript dates.

However, the equality operators ==, !=, ===, !== cannot be used to compare (the value of) dates because:

  • Two distinct objects are never equal for either strict or abstract comparisons.
  • An expression comparing Objects is only true if the operands reference the same Object.

Example:

const date1 = new Date('2026-05-16T12:00:00');
const date2 = new Date('2026-05-16T12:00:00');

// Checking for equality
if (date1.getTime() === date2.getTime()) {
    console.log("Both dates are exactly equal");
}

// Checking before or after
if (date1.getTime() < date2.getTime()) {
    console.log("date1 is before date2");
}

⚝ Try this example on CodeSandbox

Q. What are closures?

A closure is the combination of a function and the lexical environment within which that function was declared. i.e, it is an inner function that has access to the outer or enclosing function's variables.

Closure is useful in hiding implementation detail in JavaScript. In other words, it can be useful to create private variables or functions.

Lexical Scope:

In lexical scoping, free variables must belong to a parent scope. JavaScript uses lexical scoping — a function's scope is determined by where it is declared, not where it is called.

Example 01:

/**
 * Lexical Scope
 **/

function init() {
  let name = "JavaScript closures"; // name is a local variable created by init
  function displayName() {
    // displayName() is the inner function, a closure
    console.log(name); // use variable declared in the parent function
  }
  return displayName;
}

var closure = init();
closure(); // Output: "JavaScript closures"

As per the above code, the inner function displayName() has access to the variable name from the outer function init(), even after init() has returned. This is a closure.

⚝ Try this example on CodeSandbox

Example 02: Private Counter

A common use of closures is to create private state that cannot be accessed or modified directly from outside.

/**
 * Closure - Private Counter
 **/

function makeCounter() {
  let count = 0; // private variable

  return {
    increment() { count++; },
    decrement() { count--; },
    getCount()  { return count; }
  };
}

const counter = makeCounter();
counter.increment();
counter.increment();
counter.increment();
counter.decrement();
console.log(counter.getCount()); // Output: 2

// count is not directly accessible
console.log(typeof count); // Output: "undefined"

The variable count is encapsulated inside makeCounter and can only be modified through the returned methods.

Q. What is callback() function in javascript?

A callback function is a function passed into another function as an argument, which is then invoked inside the outer function to complete some kind of routine or action.

Example:

/**
 * Callback function
 */
function greet(name, callback) {
  console.log('Hello, ' + name);
  callback();
}

function sayBye() {
  console.log('Goodbye!');
}

greet('Alice', sayBye);
// Hello, Alice
// Goodbye!

Key points:

  • Callbacks can be named or anonymous functions
  • They enable asynchronous and non-blocking code
  • Overusing nested callbacks leads to "callback hell" — solved by Promises or async/await

Note: callbacks are often used to continue code execution after an asynchronous operation has completed — these are called asynchronous callbacks..

⚝ Try this example on CodeSandbox

Q. How to avoid callback hell in javascript?

Callback hell occurs when asynchronous operations are deeply nested inside one another, creating an unreadable "Pyramid of Doom"

Example:

// Callback hell
getUser(id, function(user) {
  getOrders(user, function(orders) {
    getDetails(orders[0], function(details) {
      process(details, function(result) {
        console.log(result); // deeply nested
      });
    });
  });
});

Techniques for avoiding callback hell:

1. Promises — chain .then() instead of nesting:

getUser(id)
  .then(user => getOrders(user))
  .then(orders => getDetails(orders[0]))
  .then(details => process(details))
  .then(result => console.log(result))
  .catch(err => console.error(err));

2. async/await — most readable, looks synchronous:

async function run() {
  try {
    const user    = await getUser(id);
    const orders  = await getOrders(user);
    const details = await getDetails(orders[0]);
    const result  = await process(details);
    console.log(result);
  } catch (err) {
    console.error(err);
  }
}

3. Named functions — extract callbacks into separate named functions:

function onDetails(details) { process(details).then(console.log); }
function onOrders(orders)   { getDetails(orders[0]).then(onDetails); }
function onUser(user)       { getOrders(user).then(onOrders); }

getUser(id).then(onUser);

4. Promise.all — run independent async operations in parallel:

const [users, orders] = await Promise.all([getUsers(), getOrders()]);

Note: Recommended approach: Use async/await for sequential logic and Promise.all for parallel operations.

Q. How do you encode an URL?

JavaScript provides two built-in functions for URL encoding:

1. encodeURIComponent() — encodes a single URI component (query param value, path segment):

const param = 'hello world & name=John';
encodeURIComponent(param); // 'hello%20world%20%26%20name%3DJohn'

2. encodeURI() — encodes a full URL, preserving special URL characters (:, /, ?, #, &, =):

const url = 'https://example.com/search?q=hello world&lang=en';
encodeURI(url); // 'https://example.com/search?q=hello%20world&lang=en'

Key difference:

FunctionEncodesPreserves
encodeURIComponentEverything except A-Z a-z 0-9 - _ . ! ~ * ' ( )Nothing special
encodeURISpaces and unsafe chars: / ? # & = @ ! $ ' ( ) * + , ; ~

When to use which:

const base  = 'https://example.com/search';
const query = 'hello world & more';

// Correct: encode only the param value
const url = `${base}?q=${encodeURIComponent(query)}`;
// https://example.com/search?q=hello%20world%20%26%20more

// Wrong: encodeURI won't encode & in query values
encodeURI(`${base}?q=${query}`);
// https://example.com/search?q=hello%20world%20&%20more  ← & breaks the query

Rule of thumb: Use encodeURIComponent for query parameter values; use encodeURI for a complete URL.

Q. How do you decode an URL?

The decodeURI() function is used to decode a Uniform Resource Identifier (URI) previously created by encodeURI().

1. decodeURIComponent() — decodes a single encoded URI component:

decodeURIComponent('hello%20world%20%26%20name%3DJohn');
// 'hello world & name=John'

2. decodeURI() — decodes a fully encoded URL, leaving valid URL structure characters intact:

decodeURI('https://example.com/search?q=hello%20world&lang=en');
// 'https://example.com/search?q=hello world&lang=en'

Example — reading query params:

const url = 'https://example.com/search?q=hello%20world%20%26%20more';
const raw = url.split('?q=')[1];

decodeURIComponent(raw); // 'hello world & more'

Modern alternative — use URLSearchParams which handles encoding/decoding automatically:

const params = new URLSearchParams('q=hello%20world&lang=en');
params.get('q'); // 'hello world'

Q. How function overloading works in JavaScript?

JavaScript does not support traditional function overloading (unlike Java or C#) where multiple functions can have the exact same name but different parameters. If you define two functions with the same name, the second definition will completely overwrite the first one.

Example:

function greet(name) { return `Hello, ${name}`; }
function greet(name, title) { return `Hello, ${title} ${name}`; }

greet('Alex'); // 'Hello, undefined Alex' — second definition always wins

Ways to simulate overloading:

1. Overloading by Argument Count (arguments.length):

function calculateArea(...args) {
  if (args.length === 1) {
    return Math.PI * args[0] * args[0]; // Circle area: 1 argument (radius)
  }
  if (args.length === 2) {
    return args[0] * args[1];           // Rectangle area: 2 arguments (width, height)
  }
}

console.log(calculateArea(5));     // 78.54 (Circle)
console.log(calculateArea(4, 5));  // 20 (Rectangle)

2. Overloading by Data Type (typeof / instanceof)

function add(a, b) {
  if (typeof a === 'string') return a + b;       // string concat
  if (typeof a === 'number') return a + b;       // numeric add
}

add(1, 2);       // 3
add('foo', 'bar'); // 'foobar'

3. Overloading using an Options Object

function createUser({ name, age = 18, role = 'user' } = {}) {
  return { name, age, role };
}

createUser({ name: 'Alice', age: 20 });
createUser({ name: 'Bob', age: 30, role: 'Admin' });

4. Rest parameters for variable argument count:

function sum(...nums) {
  return nums.reduce((acc, n) => acc + n, 0);
}

sum(1, 2);       // 3
sum(1, 2, 3, 4); // 10

Recommended approach: Use the options object pattern for complex cases — it's readable, flexible, and self-documenting.

Q. What is an IIFE (Immediately Invoked Function Expression)?

An IIFE (Immediately Invoked Function Expression) is a JavaScript function that is defined and executed immediately after its creation. It is a design pattern used to execute code immediately and isolate variables from the global scope.

Syntax:

(function () {
  console.log('Runs immediately');
})();

// Arrow function version
(() => {
  console.log('Also runs immediately');
})();

Example 01:

const result = (function () {
  const message = 'Hello from IIFE';
  return message;
})();

console.log(result); // 'Hello from IIFE'
console.log(typeof message); // 'undefined' — private to the IIFE

Example 02: Create a private scope

// Counter using IIFE
const counter = (function () {
  let count = 0; // private
  return {
    increment() { return count++; },
    value()     { return count; }
  };
})();

console.log(counter.increment()); // 0
console.log(counter.value());     // 1
//console.log(count);               // ReferenceError: count is not defined

Use cases:

  • Data Privacy: Variables declared inside an IIFE cannot be accessed from the outside world. This protects your code from polluting the global scope.

  • Avoiding Naming Collisions: It prevents variables from accidentally overwriting other variables in third-party libraries or scripts.

  • State Preservation: Historically, it was used inside loops to lock in the current value of a variable during asynchronous operations.

Note:

With ES6 let/const and modules, IIFEs are less common today — block scoping and module scope solve the same problems. However, IIFEs are still useful for one-time initialization logic and encapsulating setup code.

Q. What is recursion in JavaScript?

Recursion is a technique in which a function calls itself until it reaches a base case (a condition that stops further calls). Every recursive function must have a base case to avoid infinite loops and stack overflow errors.

Example 01: Factorial

function factorial(n) {
  if (n <= 1) return 1;        // base case
  return n * factorial(n - 1); // recursive call
}

console.log(factorial(5)); // 120 (5 × 4 × 3 × 2 × 1)
console.log(factorial(0)); // 1

Example 02: Fibonacci

function fibonacci(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

console.log(fibonacci(6)); // 8 (0, 1, 1, 2, 3, 5, 8)

Example 03: Flatten a nested array

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

console.log(flattenArray([1, [2, [3, [4]], 5]])); // [1, 2, 3, 4, 5]

Recursion vs Iteration:

  • Recursion is more readable for tree/graph traversal, nested structures, and divide-and-conquer problems
  • Iteration is more performant for simple loops — use it when recursion depth could be large

Note: For deeply nested structures, iterative solutions with an explicit stack are preferred over recursion to avoid stack-overflow errors.

Q. What are nested functions in JavaScript?

A nested function is a function defined inside another function. The inner function has access to the outer function's variables and parameters through closure, but the outer function cannot access the inner function's variables.

Example:

function outerFunction(outerValue) {
  const outerVar = 'I am outer';

  function innerFunction(innerValue) {
    // inner can access outer\'s scope
    console.log(outerVar);       // 'I am outer'
    console.log(outerValue);     // passed argument
    console.log(innerValue);     // inner argument
  }

  innerFunction('I am inner');
}

outerFunction('hello');

Use case — private helper functions:

function processOrder(order) {
  function validate(o) {
    return o.quantity > 0 && o.price > 0;
  }

  function calculateTotal(o) {
    return o.quantity * o.price;
  }

  if (!validate(order)) throw new Error('Invalid order');
  return calculateTotal(order);
}

console.log(processOrder({ quantity: 3, price: 15 })); // 45

The inner helpers validate and calculateTotal are private to processOrder and not accessible from outside.

Q. What are side effects in JavaScript functions?

A side effect is any observable change that a function makes to the state outside its own scope — such as modifying a global variable, mutating an argument, writing to the DOM, or making a network request.

Functions with side effects:

let count = 0;

function increment() {
  count++; // modifies external state — side effect
}

increment();
console.log(count); // 1

Pure function (no side effects):

function add(a, b) {
  return a + b; // no external mutation, same input always gives same output
}

Common sources of side effects:

SourceExample
Mutating argumentsarr.push(item) inside a function
Global state changeModifying window or module-level variables
I/O operationsconsole.log, network calls, file writes
DOM manipulationdocument.getElementById('x').textContent = '...'

Why it matters: Functions with side effects are harder to test, reason about, and reuse. Functional programming favors isolating side effects to the edges of a system while keeping the core logic pure.

Q. What is point-free style in JavaScript?

Point-free style (also called tacit programming) is a way of defining functions without explicitly mentioning the arguments they operate on. Instead, functions are composed of other functions using combinators like map, filter, reduce, or a compose/pipe utility.

Without point-free:

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

// explicitly names the argument n
const doubled = numbers.map(n => n * 2);

With point-free:

const double = x => x * 2;

// double is passed directly — no explicit argument mentioned
const doubled = numbers.map(double);

More complex example:

const trim = str => str.trim();
const toUpperCase = str => str.toUpperCase();
const words = str => str.split(' ');

// Point-free pipeline using a pipe utility
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);

const process = pipe(trim, toUpperCase, words);

console.log(process('  hello world  ')); // ['HELLO', 'WORLD']

Point-free style leads to cleaner, more declarative code but can reduce readability when overused.

Q. What is function composition in JavaScript?

Function composition is the process of combining two or more functions where the output of one function becomes the input of the next. It is a core concept in functional programming.

compose applies functions right-to-left; pipe applies them left-to-right.

compose:

const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);

const add10  = x => x + 10;
const double = x => x * 2;
const square = x => x * x;

// square( double( add10(5) ) ) => square(double(15)) => square(30) => 900
const transform = compose(square, double, add10);
console.log(transform(5)); // 900

pipe (left-to-right):

const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);

// add10(5) => double(15) => square(30) => 900
const transform = pipe(add10, double, square);
console.log(transform(5)); // 900

Real-world example:

const trim       = str => str.trim();
const toLower    = str => str.toLowerCase();
const removeSpaces = str => str.replace(/\s+/g, '-');

const slugify = pipe(trim, toLower, removeSpaces);

console.log(slugify('  Hello World  ')); // 'hello-world'

L2: Intermediate (Junior-Mid / Developer)


# 10. Scope & Closures


Q. What is lexical scope in JavaScript, and how does it differ from dynamic scope?

Lexical scope (also called static scope) means that the scope of a variable is determined at author time — by where the function is written in the source code, not where it is called from. JavaScript uses lexical scoping exclusively. When the JavaScript engine (V8) parses the source, it builds a scope chain based on the physical nesting of functions and blocks; this chain never changes at runtime.

Dynamic scope, by contrast, resolves variable references based on the call stack at runtime. Languages like Bash and early Lisp dialects use dynamic scope. JavaScript has no dynamic scope (though this is sometimes confused for it because it is determined by the call site — this binding is separate from scope).

Key rules of lexical scope:

  • Inner functions can access variables declared in outer functions (scope chain lookup).
  • Outer functions cannot access variables declared in inner functions.
  • The lookup travels outward until the global scope is reached; if not found, ReferenceError is thrown (in strict mode) or an implicit global is created (sloppy mode).
// Lexical scope example
const greeting = 'Hello';          // global scope

function outer() {
  const name = 'Alice';            // outer function scope

  function inner() {
    const punctuation = '!';       // inner function scope
    // inner can see: punctuation, name, greeting (chain lookup)
    console.log(`${greeting}, ${name}${punctuation}`);
  }

  inner(); // 'Hello, Alice!'
  // console.log(punctuation); // ReferenceError — outer cannot see inner\'s variables
}

outer();

Real-World Use Case:

React's hook system relies on lexical scope. When you write a custom hook like useCounter, the state variables declared inside it are lexically scoped to that hook invocation — they cannot bleed into unrelated components, making stateful logic safely composable and predictable in a large codebase.

Q. Explain the difference between var, let, and const with respect to scope.

Featurevarletconst
ScopeFunctionBlockBlock
HoistedYes (as undefined)Yes (TDZ — not initialised)Yes (TDZ — not initialised)
Re-declarableYesNoNo
Re-assignableYesYesNo (binding; object contents can mutate)
Global propertyCreates window.xNoNo

Block scope means the variable only exists within the nearest enclosing {} — an if, for, while, or plain block. var ignores block boundaries and is scoped to the enclosing function (or the global object if outside any function).

function demonstrateScope() {
  if (true) {
    var  funcScoped  = 'var';   // hoisted to function top
    let  blockScoped = 'let';   // lives only inside this if-block
    const immutable  = 'const'; // same block scope as let
  }

  console.log(funcScoped);   // 'var'    
  // console.log(blockScoped); // ReferenceError 
  // console.log(immutable);   // ReferenceError 
}

// Classic var-in-loop bug
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log('var:', i), 0); // prints 3, 3, 3
}

// Fixed with let (each iteration gets its own binding)
for (let j = 0; j < 3; j++) {
  setTimeout(() => console.log('let:', j), 0); // prints 0, 1, 2
}

Real-World Use Case:

In large React/Node.js codebases, var inside for loops iterating over async operations (e.g., firing multiple API calls) was a notorious source of race-condition bugs because all callbacks shared the same i. Migrating to let (or closure-capturing patterns) eliminated entire categories of production bugs. ESLint's no-var rule enforces this at scale.

Q. What is the Temporal Dead Zone (TDZ)?

In ES6, let bindings are not subject to "variable hoisting", which means that let declarations do not move to the top of the current execution context.

Referencing the variable in the block before the initialization results in a ReferenceError (contrary to a variable declared with var, which will just have the undefined value). The variable is in a "temporal dead zone" from the start of the block until the initialization is processed.

console.log(x); //  undefined  — var is hoisted and initialised to undefined
var x = 5;

console.log(y); //  ReferenceError: Cannot access 'y' before initialization
let y = 10;

// TDZ also applies inside blocks
{
  // TDZ for 'z' starts here ↓
  // console.log(z); // ReferenceError
  let z = 42;       // TDZ ends here, z is now initialised
  console.log(z);   // 42
}

Real-World Use Case:

TDZ prevents a subtle bug in module initialisation cycles. If module A imports from module B and module B imports from module A, const exports are protected by TDZ, causing a clear ReferenceError at the problematic access point rather than silently producing undefined computations that corrupt data — making circular-dependency bugs far easier to diagnose in large monorepo setups.

Q. What is a closure and how does JavaScript implement it internally?

A closure is a function that retains a reference to its lexical environment (the scope in which it was defined) even after that outer scope has finished executing. In V8, when a function is created, the engine attaches a hidden [[Environment]] slot pointing to the current scope's environment record. As long as the inner function is reachable, the garbage collector keeps the outer environment record alive.

Practically: closures "close over" variables — not values. They hold a live reference to the variable binding, so mutations to that variable are visible inside the closure.

function makeCounter(initialValue = 0) {
  let count = initialValue; // this variable is closed over

  return {
    increment() { return ++count; },
    decrement() { return --count; },
    reset()     { count = initialValue; return count; },
    value()     { return count; },
  };
}

const counter = makeCounter(10);
console.log(counter.increment()); // 11
console.log(counter.increment()); // 12
console.log(counter.decrement()); // 11
console.log(counter.value());     // 11

// count is NOT accessible from outside
// console.log(count); // ReferenceError

Real-World Use Case:

The Redux store implementation (createStore) uses closures to encapsulate currentState, currentReducer, and listeners. External code can only interact with state through the returned dispatch, getState, and subscribe functions — a closure-based module pattern that enforces immutable, predictable state management at scale.

Q. How do closures enable the Module Pattern and data encapsulation?

The Module Pattern (and its IIFE variant) uses closures to create private state — something JavaScript classes did not natively support before ES2022 private fields (#). An IIFE runs once, defines private variables, and returns a public API. Only the returned methods can access the private variables via closure.

Example:

// Classic Module Pattern (IIFE-based)
const BankAccount = (() => {
  // Private state — not accessible from outside
  let _balance   = 0;
  const _history = [];

  function _log(action, amount) {
    _history.push({ action, amount, balance: _balance, ts: Date.now() });
  }

  // Public API
  return {
    deposit(amount) {
      if (amount <= 0) throw new RangeError('Amount must be positive');
      _balance += amount;
      _log('deposit', amount);
      return this;
    },
    withdraw(amount) {
      if (amount > _balance) throw new Error('Insufficient funds');
      _balance -= amount;
      _log('withdrawal', amount);
      return this;
    },
    getBalance()  { return _balance; },
    getHistory()  { return [..._history]; }, // defensive copy
  };
})();

BankAccount.deposit(500).deposit(200).withdraw(100);
console.log(BankAccount.getBalance()); // 600
console.log(BankAccount._balance);     // undefined — truly private

Real-World Use Case:

Before ES modules became ubiquitous, large libraries like jQuery, Lodash, and early Angular 1.x used the Module Pattern (IIFE + closure) to avoid polluting the global namespace and to encapsulate internal state. Today, ES modules provide file-level encapsulation, but the closure-based pattern is still used inside modules for class-alternative encapsulation and for factories returned from hooks or composables.

Q. What is a closure memory leak and how do you prevent it?

A closure memory leak occurs when a closure unintentionally keeps a large object (such as a DOM node, a large dataset, or a complete HTTP response) in memory by holding a reference in its closed-over scope long after the object is needed. The V8 garbage collector cannot reclaim the memory because the closure is still reachable.

Common patterns that cause leaks:

  1. Event listeners not removed — the listener closes over component state, keeping the entire component tree alive.
  2. Timers not cleared — setInterval callbacks closing over large objects run indefinitely.
  3. Detached DOM nodes — a closure holds a reference to a DOM node that has been removed from the document.
//  Leak: interval callback closes over largeData forever
function startPolling() {
  const largeData = new Array(100_000).fill('payload');

  const id = setInterval(() => {
    console.log(largeData.length); // largeData is never freed
  }, 1000);

  // Missing: return id so the caller can clearInterval(id)
}

//  Fix: return cleanup, or null out the reference
function startPollingFixed() {
  const largeData = new Array(100_000).fill('payload');

  const id = setInterval(() => {
    process(largeData);
  }, 1000);

  return () => clearInterval(id); // caller must invoke this to free memory
}

const stopPolling = startPollingFixed();
// Later, when no longer needed:
stopPolling(); // interval cleared, largeData eligible for GC

React-specific pattern:

useEffect(() => {
  const controller = new AbortController();

  fetch('/api/data', { signal: controller.signal })
    .then(res => res.json())
    .then(setData);

  return () => controller.abort(); // cleanup prevents stale closure + memory leak
}, []);

Real-World Use Case:

In Single Page Applications (SPAs), route navigation causes component unmounting. Event listeners or WebSocket message handlers registered on mount but not removed on unmount keep the entire component closure alive for the application's lifetime, causing memory growth that crashes long-running sessions (e.g., trading dashboards, kiosk apps). React's useEffect cleanup function pattern was designed specifically to address this.

Q. Explain the IIFE pattern and when you would use it over ES modules.

An Immediately Invoked Function Expression (IIFE) is a function that is defined and called in a single expression. Its primary purpose is to create an isolated scope, preventing variable declarations from leaking into the surrounding (often global) scope.

(function() { /* body */ })();
// or with arrow functions:
(() => { /* body */ })();

When to still use IIFEs over ES modules:

  • Browser script tags without a bundler — when you cannot use type="module" and need scope isolation.
  • Polyfill / library UMD bundles — output format used by tools like Rollup/Webpack to expose libraries as window.MyLib while avoiding global pollution.
  • Initialisation code that must run exactly once and should not be importable.
  • Micro-optimisation in hot paths — in rare engine-specific scenarios, an IIFE can help the optimiser treat a block as a separate compilation unit.

Example:

// UMD wrapper (simplified) — the output of many bundlers
(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined'
    ? module.exports = factory()                    // CommonJS (Node.js)
    : typeof define === 'function' && define.amd
      ? define(factory)                             // AMD (RequireJS)
      : (global.MyLib = factory());                 // Browser global
}(typeof globalThis !== 'undefined' ? globalThis : this, function () {
  'use strict';

  const VERSION = '1.0.0';        // private
  return { VERSION, greet: (name) => `Hello, ${name}` }; // public API
}));

Real-World Use Case:

Webpack wraps each module in an IIFE in its legacy output mode to simulate module scope in environments lacking native ES module support (Internet Explorer, older Node.js). When targeting modern environments, Webpack and Rollup emit native ES modules instead, removing the IIFE overhead. Understanding this is essential when configuring output.libraryTarget in webpack configs for distributing npm packages.

Q. How does the scope chain work during variable lookup, and what is the performance implication?

When a variable is referenced, the JavaScript engine performs a scope chain lookup: it starts at the current scope's environment record and walks up through enclosing records until the variable is found or the global scope is exhausted.

V8 optimises this through:

  1. Hidden classes / shapes — objects have stable layouts; property access is compiled to a direct memory offset.
  2. Inline caching (IC) — after the first lookup, V8 caches the resolved location. Repeated access to the same variable from the same call site is O(1).
  3. Closure context allocation — V8's Turbofan compiler performs context allocation analysis. Variables that are only used locally stay on the stack (fast). Variables that are closed over are promoted to a heap-allocated context object — slightly slower due to heap allocation and GC pressure.

Performance implication: deeply nested closures that close over many large variables create long-lived heap contexts. Flattening the closure chain or copying only the needed values can improve GC throughput.

Example:

//  Unnecessarily deep scope chain — engine must walk up 3 levels on each access
function level1() {
  const config = loadHeavyConfig(); // heap-allocated context (closed over)

  function level2() {
    function level3() {
      return config.timeout; // 3-level chain lookup
    }
    return level3;
  }
  return level2;
}

//  Flatten: extract only needed value to the immediate scope
function level1Optimised() {
  const { timeout } = loadHeavyConfig(); // only close over what is needed

  function level2() {
    function level3() {
      return timeout; // 1 var, still 3-level chain but smaller context object
    }
    return level3;
  }
  return level2;
}

Real-World Use Case:

In Node.js request handlers that process millions of requests per minute (e.g., an Express.js API gateway), middleware functions close over the app config object. Profiling with --prof (V8 tick profiler) or Chrome DevTools' flame graph reveals when heap contexts from closures apply GC pressure. The optimisation is to destructure only required config fields at the module level — keeping the closure context objects small and promoting faster minor GC cycles.

Q. What is the difference between scope and context (this) in JavaScript?

These two concepts are frequently confused but are entirely independent mechanisms:

ScopeContext (this)
Determined byWhere code is written (lexical)How a function is called (dynamic)
StoresVariable bindingsThe object a function is operating upon
Changed byImpossible at runtimecall, apply, bind, arrow functions, class fields
Applies toAll identifiersOnly the this keyword

Arrow functions are the intersection: they do not create their own this binding — they inherit this lexically from the enclosing scope at definition time. This makes them behave like closures for this.

Example:

const timer = {
  label: 'Countdown',
  seconds: 3,

  //  Regular function: `this` is determined by caller (lost in setTimeout callback)
  startBroken() {
    setTimeout(function () {
      console.log(this.label); // undefined — `this` is global/undefined (strict mode)
    }, 1000);
  },

  //  Arrow function: `this` inherited lexically from startFixed\'s scope
  startFixed() {
    setTimeout(() => {
      console.log(this.label); // 'Countdown' 
    }, 1000);
  },

  //  Explicit bind alternative
  startWithBind() {
    setTimeout(function () {
      console.log(this.label); // 'Countdown' 
    }.bind(this), 1000);
  },
};

timer.startFixed();

Real-World Use Case:

React class components historically required .bind(this) in the constructor for event handlers because a regular function passed as a JSX prop loses its this binding. The shift to arrow function class fields (handleClick = () => { ... }) and then to functional components with hooks eliminated this entire category of this-related bugs — demonstrating that understanding the scope vs. context distinction is foundational to making informed decisions about component architecture.

# 11. Hoisting


Q. What is hoisting in JavaScript, and what gets hoisted?

Hoisting is a JavaScript mechanism where variable, function, class, and import declarations are conceptually moved to the top of their containing scope during the compilation phase, before the code executes. Hoisting allows functions to be safely used in code before they are declared.

Example 01: Variable Hoisting

console.log(message); // output: undefined
var message = "The variable Has been hoisted";

The above code looks like as below to the interpreter,

var message;
console.log(message);
message = "The variable Has been hoisted";

Example 02: let and const hoisting

All declarations (function, var, let, const and class) are hoisted in JavaScript, while the var declarations are initialized with undefined, but let and const declarations remain uninitialized.

console.log(x);
let x = 10;

// Output: ReferenceError: x is not defined

Example 03: Function Hoisting

One of the advantages of hoisting is that it lets you use a function before you declare it in your code.

getName("Sadhika Sandal");

function getName(name) {
  console.log("Hello " + name);
}

Output:

Hello Sadhika Sandal

They will only get initialized when their lexical binding (assignment) is evaluated during runtime by the JavaScript engine. This means we can't access the variable before the engine evaluates its value at the place it was declared in the source code. This is what we call Temporal Dead Zone (TDZ), A time span between variable creation and its initialization where they can't be accessed.

What gets hoisted and how:

DeclarationHoisted?Initialised to
varYesundefined
letYes (TDZ)uninitialised — ReferenceError if accessed before declaration
constYes (TDZ)uninitialised — ReferenceError if accessed before declaration
function declarationYesFull function body
function expression (var f = function(){})Partiallyvar f hoisted as undefined; body is not hoisted
class declarationYes (TDZ)uninitialised — ReferenceError if accessed before declaration
importYesFully evaluated before module body runs

Real-World Use Case:

Node.js module systems and build tools like Webpack rely on function-declaration hoisting to allow modules to call utility functions defined later in the same file — enabling a readable "public API at the top, implementation details below" code style. However, misunderstanding var hoisting is responsible for a large class of production bugs in legacy jQuery/Angular 1 codebases, which is why the Airbnb ESLint config bans var and mandates let/const.

Q. In which case the function definition is not hoisted in JavaScript?

Let us take the following function expression

var foo = function foo() {
  return 12;
}

In JavaScript var-declared variables and functions are hoisted. Let us take function hoisting first. Basically, the JavaScript interpreter looks ahead to find all the variable declaration and hoists them to the top of the function where It is declared. For example:

foo(); // Here foo is still undefined
var foo = function foo() {
  return 12;
};

The code above behind the scene look something like this:

var foo = undefined;
foo(); // Here foo is undefined
foo = function foo() {
  // Some code stuff
}
var foo = undefined;
foo = function foo() {
  // Some code stuff
}
foo(); // Now foo is defined here

Q. How does hoisting behave differently for function declarations versus function expressions and arrow functions?

  • Function declaration (function foo() {}): The entire function — name and body — is hoisted. The function is callable anywhere in its scope, even before the line it appears on.
  • Function expression (const foo = function() {}): Only the variable declaration is hoisted (if var); the assignment — the function value itself — is not. Calling it before the line throws TypeError (not ReferenceError) because the variable exists but holds undefined.
  • Arrow function (const foo = () => {}): Same as function expression — it is always assigned to a variable, so the same rules apply.

Example:

// 1. Function Declaration — fully hoisted 
console.log(add(2, 3)); // 5

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

// 2. Function Expression with var — var hoisted as undefined 
console.log(multiply); // undefined
// console.log(multiply(2, 3)); // TypeError: multiply is not a function

var multiply = function (a, b) {
  return a * b;
};

// 3. Function Expression with let — TDZ 
// console.log(divide(10, 2)); // ReferenceError
const divide = (a, b) => a / b;

// 4. Practical gotcha: conditional function declaration (avoid — browser-dependent)
if (true) {
  function conditionalFn() { return 'inside if'; }
}
// Behaviour in strict mode: conditionalFn is block-scoped to the if-block
// Behaviour in sloppy mode: varies by browser — avoid this pattern entirely

Real-World Use Case:

In large React component files, developers often define helper arrow functions (const formatDate = (d) => ...) after the component function that uses them. Because the component is a const arrow function itself, calling formatDate inside it only works if formatDate is defined above — otherwise a ReferenceError occurs at module load time. Understanding hoisting rules guides file organisation: place utility declarations before their consumers, or use function declarations when ordering flexibility matters.

Q. How does hoisting interact with the class declaration?

Class declarations are hoisted to the top of their block scope — like let and const — but they are not initialised until the declaration is evaluated. This means they sit in the Temporal Dead Zone (TDZ) and accessing a class before its declaration throws a ReferenceError, not a TypeError. This is a deliberate choice to prevent confusing inheritance hierarchies from being constructed before base classes are fully defined.

Example:

//  Class in TDZ — accessing before declaration throws ReferenceError
// const p = new Person('Alice'); // ReferenceError: Cannot access 'Person' before initialization

class Person {
  constructor(name) {
    this.name = name;
  }
  greet() {
    return `Hi, I am ${this.name}`;
  }
}

const p = new Person('Alice'); // 
console.log(p.greet());        // 'Hi, I am Alice'

// Class expression — same as function expression: name is in TDZ if const/let
const Animal = class {
  constructor(species) {
    this.species = species;
  }
};

const dog = new Animal('Canis lupus'); // 

Practical implication — class vs. function constructor:

// Function constructor: hoisted entirely — can be used before declaration
const f = new Foo(); // 
function Foo() { this.x = 1; }

// Class: TDZ — cannot be used before declaration
// const b = new Bar(); //  ReferenceError
class Bar { constructor() { this.x = 1; } }

Real-World Use Case:

When splitting large Node.js services into files with circular require() references, class-based modules that are accessed during the require() cycle before the class declaration evaluates throw ReferenceError. Understanding the TDZ for classes helps engineers design module load order carefully — or avoid circular dependencies by refactoring to dependency injection patterns (e.g., passing the class as a parameter rather than importing it at the module level).

Q. What is the hoisting behaviour inside let/const blocks, and how does it affect switch statements?

let and const are block-scoped and hoisted within their block to the TDZ. A switch statement's {} is a single block — all case clauses share the same block scope. This causes a subtle bug: a let declared in one case is technically in scope (hoisted) for all other cases in the same switch block, causing a SyntaxError if another case attempts to declare the same name.

Example:

//  SyntaxError: Identifier 'result' has already been declared
function getLabel(status) {
  switch (status) {
    case 'active':
      let result = 'User is active';   // hoisted to top of switch block
      return result;
    case 'inactive':
      let result = 'User is inactive'; // SyntaxError — same block, same name
      return result;
  }
}

//  Fix 1: wrap each case in its own block
function getLabelFixed(status) {
  switch (status) {
    case 'active': {
      let result = 'User is active';
      return result;
    }
    case 'inactive': {
      let result = 'User is inactive'; // own block — no conflict
      return result;
    }
    default:
      return 'Unknown';
  }
}

//  Fix 2: prefer object lookup over switch for simple mappings
const STATUS_LABELS = {
  active:   'User is active',
  inactive: 'User is inactive',
};
const getLabel2 = (status) => STATUS_LABELS[status] ?? 'Unknown';

Real-World Use Case:

This exact bug has appeared in production Redux reducers where developers switched from var to let during a linting migration. All case branches in a reducer's switch share one block, so re-declaring let draft in multiple cases causes a SyntaxError caught only at parse time — blocking the entire app from loading. The standard Redux Toolkit solution is to use Immer-based createSlice instead, which replaces the switch reducer pattern entirely.

Q. How does hoisting affect the execution order of multiple var declarations and function declarations with the same name?

When multiple var declarations or function declarations share the same identifier in the same scope, the engine applies specific precedence rules during the compilation phase:

  1. var declarations are merged into one — only one undefined slot is allocated; whichever assignment executes last wins at runtime.
  2. Function declarations override var declarations of the same name during hoisting — the function body wins over var's undefined.
  3. Multiple function declarations with the same name: the last one in source order wins (it overwrites the earlier one during hoisting).

Example:

// What does the programmer see:
var x = 1;
function x() { return 'fn'; }
var x = 2;

// What the engine processes during compilation:
// 1. var x → undefined (registered)
// 2. function x → x = function() { return 'fn'; } (overwrites var)
// 3. (execution phase) x = 1
// 4. (execution phase) x = 2
console.log(x); // 2 — last assignment wins

// Multiple function declarations — last one wins
function greet() { return 'Hello'; }
function greet() { return 'Hi'; }   // overwrites the first
console.log(greet()); // 'Hi'

// var + function same name — function hoisted OVER var during compilation
console.log(typeof answer); // 'function' (not 'undefined')
var answer = 42;
function answer() { return 'forty-two'; }
console.log(answer); // 42 — runtime assignment overrides the function

Why this matters architecturally:

In large legacy codebases (pre-modules), concatenating multiple script files together frequently caused silent name collisions of this kind. Two files each declaring var config = ... or function init() {} would silently overwrite each other. The last file in the bundle always won, causing intermittent and environment-specific bugs that only appeared in production where file concatenation order differed from the development setup.

Real-World Use Case:

This collision class of bugs was a primary driver for the adoption of the Module Pattern (IIFE-based), then AMD/CommonJS, and ultimately ES modules — each solution adding a layer of scope isolation to prevent hoisting-based name collisions. Modern bundlers (Webpack, Rollup) assign each module its own scope by wrapping it, making cross-module var collision impossible. Understanding this history explains why "use strict" and ES modules are defaults in all modern toolchains.

# 12. ES6 Features


Q. How do arrow functions differ from regular functions, and when should you not use them?

Arrow functions (=>) introduced in ES2015 are a concise syntax for writing functions, but they are not simply shorthand for function. They have fundamental semantic differences:

FeatureRegular FunctionArrow Function
this bindingDynamic — determined by call siteLexical — inherits from enclosing scope
arguments objectAvailableNot available (use rest params)
new (constructor)Can be usedThrows TypeError
prototype propertyHas oneundefined
super / new.targetOwn bindingInherited from enclosing scope
Method shorthandUsable (with caution)Avoid for object methods needing this

When NOT to use arrow functions:

  1. Object methods that need dynamic this (obj.method() — arrow's this is the enclosing scope, not obj).
  2. Constructor functions — arrow functions cannot be called with new.
  3. Event handlers that need this to refer to the DOM element (element.addEventListener('click', () => {}) — this is the outer context, not the element).
  4. Generator functions — arrow syntax does not support function*.
  5. Functions that need their own arguments object.
// --- Concise bodies ---
const double  = x => x * 2;              // single param, expression body
const add     = (a, b) => a + b;         // multiple params
const toUpper = str => ({ upper: str.toUpperCase() }); // returning an object literal — wrap in ()

// --- `this` lexical binding ---
class Timer {
  constructor() {
    this.ticks = 0;
  }

  start() {
    // Arrow function: `this` is the Timer instance (lexical)
    this.interval = setInterval(() => {
      this.ticks++;
      console.log(this.ticks);
    }, 1000);
  }

  stop() {
    clearInterval(this.interval);
  }
}

// --- When NOT to use arrow ---
const counter = {
  count: 0,
  //  Arrow: `this` is the module/global scope, not counter
  incrementBad: () => { counter.count++; },
  //  Method shorthand: `this` is counter
  incrementGood() { this.count++; },
};

//  Arrow as constructor
// const p = new (() => {}); // TypeError: (intermediate value) is not a constructor

//  No own `arguments`
const fn = () => {
  // console.log(arguments); // ReferenceError in strict mode / module scope
};
const fn2 = (...args) => args; //  Use rest params instead

Real-World Use Case:

React's functional components and hooks use arrow functions almost exclusively for event handlers passed as JSX props (onClick={() => dispatch(action)}). Because these arrow functions inherit this from the component scope (which in a functional component is irrelevant — there is no this), the lexical binding is harmless and the concise syntax reduces boilerplate. Conversely, in Express.js route handlers or Mongoose middleware that rely on this referring to the document/request, using arrow functions is a common production bug — the official Mongoose docs explicitly warn against it.

Q. What are template literals, and what are their advanced capabilities beyond string interpolation?

Template literals (backtick strings) introduced in ES2015 offer four key capabilities over traditional string literals:

  1. String interpolation — embed any expression with ${expr}.
  2. Multi-line strings — newlines are literal; no \n needed.
  3. Expression evaluation — any valid JavaScript expression inside ${} including ternaries, function calls, and arithmetic.
  4. Tagged templates — a function (tag) is called with the string parts and interpolated values, enabling domain-specific languages (DSLs), sanitisation, and localisation.
// 1. Interpolation
const user = { name: 'Alice', score: 42 };
console.log(`Player: ${user.name}, Score: ${user.score * 2}`); // Player: Alice, Score: 84

// 2. Multi-line
const html = `
  <article>
    <h1>${user.name}</h1>
    <p>Score: ${user.score}</p>
  </article>
`.trim();

// 3. Expressions inside ${}
const items = ['apple', 'banana', 'cherry'];
console.log(`You have ${items.length} item${items.length !== 1 ? 's' : ''}.`);
// 'You have 3 items.'

// 4. Tagged template literals
function highlight(strings, ...values) {
  return strings.reduce((result, str, i) =>
    result + str + (values[i] !== undefined ? `<mark>${values[i]}</mark>` : ''),
    ''
  );
}

const product = 'JavaScript';
const year = 2015;
console.log(highlight`${product} was standardised in ${year}.`);
// '<mark>JavaScript</mark> was standardised in <mark>2015</mark>.'

// SQL injection prevention with tagged templates (concept)
function sql(strings, ...values) {
  const sanitised = values.map(v => sanitiseInput(v)); // escape each interpolated value
  return strings.reduce((q, str, i) => q + str + (sanitised[i] ?? ''), '');
}
function sanitiseInput(v) { return String(v).replace(/'/g, "''"); }

const userId = "1' OR '1'='1"; // SQL injection attempt
const query = sql`SELECT * FROM users WHERE id = '${userId}'`;
// SELECT * FROM users WHERE id = '1'' OR ''1''=''1'  — safely escaped

Real-World Use Case:

The styled-components and emotion CSS-in-JS libraries are built entirely on tagged template literals. When you write styled.div`color: ${props => props.primary ? 'blue' : 'grey'};`, the library's tag function receives the static CSS parts and the dynamic value functions, generates a unique class name, injects a <style> rule, and tracks theme changes — all without any custom parser. This demonstrates that tagged templates can implement fully featured DSLs (CSS, GraphQL, SQL, i18n) natively in JavaScript with zero dependencies.

Q. Explain destructuring assignment — object, array, nested, and with defaults.

Destructuring is a syntax for unpacking values from arrays or properties from objects into distinct variables in a single statement. It is purely syntactic sugar — the engine converts it to a series of property accesses and assignments.

// ── Object destructuring ──────────────────────────────────────────────────────

const user = { id: 1, name: 'Alice', role: 'admin', address: { city: 'NYC', zip: '10001' } };

// Basic
const { id, name } = user;

// Rename (alias)
const { name: username, role: userRole } = user;
console.log(username, userRole); // 'Alice', 'admin'

// Default values (used when property is undefined)
const { theme = 'light', lang = 'en' } = user;
console.log(theme, lang); // 'light', 'en'

// Nested destructuring
const { address: { city, zip } } = user;
console.log(city, zip); // 'NYC', '10001'

// Rest in object destructuring
const { id: userId, ...rest } = user;
console.log(userId); // 1
console.log(rest);   // { name: 'Alice', role: 'admin', address: {...} }

// ── Array destructuring ───────────────────────────────────────────────────────

const rgb = [255, 128, 0];

const [red, green, blue] = rgb;
const [r, , b] = rgb;    // skip elements with empty commas
const [first, ...others] = rgb; // rest in array

// Swap variables without temp
let a = 1, bVal = 2;
[a, bVal] = [bVal, a];
console.log(a, bVal); // 2, 1

// Default values
const [x = 0, y = 0, z = 0] = [10, 20];
console.log(x, y, z); // 10, 20, 0

// ── Function parameter destructuring ─────────────────────────────────────────

function renderCard({ title, subtitle = 'No subtitle', tags = [] } = {}) {
  return `<h1>${title}</h1><h2>${subtitle}</h2><ul>${tags.map(t => `<li>${t}</li>`).join('')}</ul>`;
}

renderCard({ title: 'Hello', tags: ['js', 'es6'] });
renderCard(); //  uses defaults — the `= {}` prevents TypeError when called with no args

// ── Mixed / deeply nested ─────────────────────────────────────────────────────

const apiResponse = {
  data: {
    users: [
      { id: 1, profile: { avatar: 'alice.png' } },
      { id: 2, profile: { avatar: 'bob.png' } },
    ],
  },
};

const { data: { users: [{ id: firstId, profile: { avatar: firstAvatar } }] } } = apiResponse;
console.log(firstId, firstAvatar); // 1, 'alice.png'

Real-World Use Case:

React's useState hook returns a two-element array precisely to enable array destructuring with custom naming: const [count, setCount] = useState(0). This pattern lets each call site choose its own variable names without the library needing to know them. Similarly, React component props are universally destructured in the function signature (function Button({ label, onClick, disabled = false }) {}), reducing prop-drilling verbosity and making prop contracts immediately visible — a pattern enforced by most team style guides and ESLint configs.

Q. What is the difference between the rest parameter and the spread operator?

Both use the ... syntax but serve opposite purposes:

Rest (...params)Spread (...iterable)
PositionLast parameter in a function signatureCall site, array literal, or object literal
PurposeCollects multiple arguments into an arrayExpands an iterable/object into individual elements
ResultOne ArrayMultiple values
// ── Rest parameter ───────────────────────────────────────────────────────────

function sum(...numbers) {           // collects all args into an array
  return numbers.reduce((acc, n) => acc + n, 0);
}
console.log(sum(1, 2, 3, 4)); // 10

function log(level, ...messages) {   // first arg fixed, rest collected
  console[level](messages.join(' '));
}
log('warn', 'Disk', 'usage', 'high'); // console.warn('Disk usage high')

// ── Spread operator ──────────────────────────────────────────────────────────

// 1. Expanding arrays
const nums = [1, 2, 3];
const more = [...nums, 4, 5];        // [1, 2, 3, 4, 5]
const copy = [...nums];              // shallow copy

// 2. Spreading into a function call
console.log(Math.max(...nums));      // 3

// 3. Merging / cloning objects (shallow)
const defaults = { theme: 'light', lang: 'en', debug: false };
const userPrefs = { theme: 'dark', fontSize: 14 };
const config = { ...defaults, ...userPrefs }; // right side wins on key collision
// { theme: 'dark', lang: 'en', debug: false, fontSize: 14 }

// 4. Spreading a string into characters
const chars = [..."hello"]; // ['h', 'e', 'l', 'l', 'o']

// 5. Converting NodeList / Set to Array
const unique = [...new Set([1, 1, 2, 3, 3])]; // [1, 2, 3]

Real-World Use Case:

Redux reducers use object spread to produce new state immutably without mutating the existing state object — a requirement of Redux's change-detection mechanism: return { ...state, isLoading: false, data: action.payload }. This replaces Object.assign({}, state, { isLoading: false }) with far more readable syntax. The V8 engine optimises spread-based object cloning with the same hidden-class mechanisms as Object.assign, so there is no meaningful performance difference in most application code.

Q. Explain ES6 Map and Set — how do they differ from plain objects and arrays, and when should you use them?

Set is a collection of unique values of any type. It maintains insertion order and provides O(1) has() lookup (hash-based internally in V8).

Map is a collection of key-value pairs where keys can be any type (including objects, functions, and NaN). Plain objects only allow string/Symbol keys.

FeatureObjectMap
Key typesString / Symbol onlyAny value (including objects)
OrderedNot guaranteed (though V8 preserves insertion order for string keys in practice)Insertion order guaranteed by spec
SizeObject.keys(obj).length — O(n)map.size — O(1)
Iterationfor...in (includes prototype) / Object.entries()for...of, forEach, direct iterators
Prototype pollutionPossible (e.g., __proto__)Not possible
JSON serialisableYes(needs custom serialiser)
FeatureArraySet
UniquenessNo — duplicates allowedYes — duplicates silently ignored
has / membershipindexOf / includes — O(n)set.has() — O(1)
OrderedIndex-basedInsertion-order
// ── Set ──────────────────────────────────────────────────────────────────────

const visited = new Set();
visited.add('/home');
visited.add('/about');
visited.add('/home');   // duplicate — silently ignored

console.log(visited.size);        // 2
console.log(visited.has('/about')); // true (O(1))

// Deduplicate an array
const tags = ['js', 'css', 'js', 'html', 'css'];
const uniqueTags = [...new Set(tags)]; // ['js', 'css', 'html']

// ── Map ──────────────────────────────────────────────────────────────────────

const cache = new Map();

// Object as key (impossible with plain objects as keys)
const req1 = { url: '/api/users' };
const req2 = { url: '/api/posts' };

cache.set(req1, { data: [1, 2, 3], ts: Date.now() });
cache.set(req2, { data: ['a', 'b'], ts: Date.now() });

console.log(cache.get(req1).data); // [1, 2, 3]
console.log(cache.size);           // 2

// Iteration
for (const [key, value] of cache) {
  console.log(key.url, value.data);
}

// Convert to / from plain object
const mapFromObj = new Map(Object.entries({ a: 1, b: 2 }));
const objFromMap = Object.fromEntries(mapFromObj);

Real-World Use Case:

WeakMap (a variant of Map with weak references and object-only keys) is used by React's Concurrent Mode internals to associate component fiber metadata with DOM nodes without preventing garbage collection of unmounted components. Set is used in many state management libraries (Zustand, Jotai) to track subscriber callbacks: O(1) add/delete/has operations make subscription management significantly faster than array-based alternatives at scale (thousands of subscribers in large dashboards).

Q. What are default parameters in ES6, and how do they interact with undefined vs. null?

ES6 default parameters allow function parameters to have a fallback value used when the argument is undefined or when no argument is provided. Crucially, passing null does not trigger the default — null is an explicit value meaning "no object here", while undefined means "value not provided".

Default expressions are evaluated lazily — they run each time the function is called with a missing argument, not once at function definition time. This allows defaults that reference other parameters or call functions.

// Basic defaults
function createUser(name, role = 'viewer', createdAt = new Date()) {
  return { name, role, createdAt };
}

createUser('Alice');                     // role: 'viewer', createdAt: <now>
createUser('Bob', 'admin');             // role: 'admin'
createUser('Carol', undefined, new Date('2024-01-01')); // role: 'viewer' (undefined triggers default)
createUser('Dave', null);              // role: null — null does NOT trigger default 

// Default referencing earlier parameter
function range(start, end = start + 10) {
  return Array.from({ length: end - start }, (_, i) => start + i);
}
console.log(range(5));    // [5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
console.log(range(1, 4)); // [1, 2, 3]

// Default calling a function (evaluated per call)
let callCount = 0;
function getDefault() { return ++callCount; }

function track(id = getDefault()) { 

Truncated — view the full README on GitHub.

es6-interview-questions
javascript
javascript-for-beginners
javascript-interview-questions
javascript-tutorials

learning-zone/javascript-basics

JavaScript Basics

HTML

1,870

1,305 commits

updated May 27, 2026

See the code

README

JavaScript Basics

Click ★ if you like the project. Your contributions are heartily ♡ welcome.



Table of Contents

L1: Fundamental (Entry-Level / Junior)

Focus: Syntax, basic logic, and standard data handling.

  • Introduction: ES6 new features
  • Variables: var, let, const.
  • Data Types: Primitives vs. Objects.
  • Operators: Arithmetic, comparison (== vs ===), and logical.
  • Numbers: isNaN, isFinite, NEGATIVE_INFINITY
  • String: slice, splice, trim, eval, replace
  • Arrays: Basic manipulation using push(), pop(), shift, indexOf etc.
  • Control Flow: Simple if/else and for loops.
  • Functions: Standard declarations and parameters.

L2: Intermediate (Junior-Mid / Developer)

Focus: ES6+ features, DOM, and common "trick" concepts.

L3: Advanced (Mid-Senior / Lead)

Focus: Asynchronous operations, performance, and internal engine mechanics. [4]

L4: Expert (Senior / Architect)

Focus: Scalability, security, and low-level optimization.

L5: Technical Lead

Focus: Code review, team standards, architectural decisions, and engineering best practices for leading a development team.

L6: Technical Architect

Focus: System-level design, scalability, security posture, micro-frontend, and cross-team JavaScript architecture.


L1: Fundamental (Entry-Level / Junior)


# 1. Introduction


Q. List out important features of JavaScript ES6?

1. Template Strings:

Template literals are string literals allowing embedded expressions.

Benefits:

  • String interpolation
  • Embedded expressions
  • Multiline strings without hacks
  • String formatting
  • String tagging for safe HTML escaping, localization and more
// String Substitution
let name = `Abhinav Sharma`;
console.log(`Hi, ${name}`); // Output: "Hi, Abhinav Sharma"

// Multiline String
let msg = `Hello \n
World`;
console.log(`${msg}`); // Output: "Hello World"

⚝ Try this example on CodeSandbox

2. Spread Operator:

Spread operator allows iterables( arrays / objects / strings ) to be expanded into single arguments/elements.

function sum(x, y, z) {
  return x + y + z;
}
const numbers = [10, 20, 30];

// Using Apply (ES5)
console.log(sum.apply(null, numbers)); // 60

// Using Spread Operator
console.log(sum(...numbers)); // 60

⚝ Try this example on CodeSandbox

2.1. Copying an array:

const fruits = ["Apple", "Orange", "Banana"];
const newFruitArray = [...fruits];

console.log(newFruitArray); 

Output:

['Apple', 'Orange', 'Banana']

⚝ Try this example on CodeSandbox

2.2. Concatenating arrays:

const arr1 = ["A", "B", "C"];
const arr2 = ["X", "Y", "Z"];

const result = [...arr1, ...arr2];

console.log(result); 

Output:

['A', 'B', 'C', 'X', 'Y', 'Z']

⚝ Try this example on CodeSandbox

2.3. Spreading elements together with an individual element:

const fruits = ["Apple", "Orange", "Banana"];
const newFruits = ["Cherry", ...fruits];

console.log(newFruits); 

Output:

['Cherry', 'Apple', 'Orange', 'Banana']

⚝ Try this example on CodeSandbox

2.4. Spreading elements on function calls:

const fruits = ["Apple", "Orange", "Banana"];

const getFruits = (f1, f2, f3) => {
  console.log(`Fruits: ${f1}, ${f2} and ${f3}`);
};

getFruits(...fruits); 

Output:

Fruits: Apple, Orange and Banana

⚝ Try this example on CodeSandbox

2.5. Spread syntax for object literals:

const obj1 = { id: 101, name: 'Rajiv Sandal' }
const obj2 = { age: 35, country: 'INDIA' }

const employee = { ...obj1, ...obj2 }

console.log(employee);

Output:

{
  "id": 101,
  "name": "Rajiv Sandal",
  "age": 35,
  "country": "INDIA"
}

3. Sets:

Sets are a new object type with ES6 (ES2015) that allow to create collections of unique values. The values in a set can be either simple primitives like strings or integers, but more complex object types like object literals or arrays can also be part of a set.

const numbers = new Set([10, 20, 20, 30, 40, 50]);

console.log(numbers); Set(5) { 10, 20, 30, 40, 50 }
console.log(typeof numbers); // Object

⚝ Try this example on CodeSandbox

4. Default Parametrs:

function add(x = 10, y = 20) {
  console.log(x + y);
}

add(10, 30); // Output: 10 + 30 = 40
add(15); // Output: 15 + 20 = 35
add(); // Output: 10 + 20 = 30

⚝ Try this example on CodeSandbox

5. repeat():

The repeat() method constructs and returns a new string which contains the specified number of copies of the string on which it was called, concatenated together.

const msg = "Hello World \n";

console.log(`${msg.repeat(3)}`);

Output:

Hello World
Hello World
Hello World

⚝ Try this example on CodeSandbox

6. Arrow Function (=>):

const add = (x, y) => x + y;

console.log(add(10, 20)); // 30

⚝ Try this example on CodeSandbox

7. Arrow function with this

/**
 * Using Arrow function
 */
const person = {
  name: "Diksha",
  actions: ["bike", "hike", "ski"],
  printActions() {
    this.actions.forEach((action) => {
      console.log(this.name + " likes to " + action);
    });
  },
};

person.printActions();

Output:

Diksha likes to bike 
Diksha likes to hike 
Diksha likes to ski

⚝ Try this example on CodeSandbox

8. Destructing Assignment:

// Destructing Assignment
const { title, price, description } = {
  title: "iPhone",
  price: 999,
  description: "The iPhone is a smartphone developed by Apple"
};

console.log(title); // iPhone
console.log(price); // 999
console.log(description); // The iPhone is a smartphone developed by Apple

⚝ Try this example on CodeSandbox

9. Generators:

A generator is a function that can stop midway and then continue from where it stopped. In short, a generator appears to be a function but it behaves like an iterator.

function* generator(num) {
  yield num + 10;
  yield num + 20;
  yield num + 30;
}
const gen = generator(10);

console.log(gen.next().value); // 20
console.log(gen.next().value); // 30
console.log(gen.next().value); // 40

⚝ Try this example on CodeSandbox

10. Symbols:

They are tokens that serve as unique IDs. We create symbols via the factory function Symbol(). Symbols primary use case is for making private object properties, which can be only of type String or Symbol (Numbers are automatically converted to Strings).

const symbol1 = Symbol();
const symbol2 = Symbol(42);
const symbol3 = Symbol("Hi");

console.log(typeof symbol1); // symbol
console.log(symbol3.toString()); // Symbol(Hi)
console.log(Symbol("Hi") === Symbol("Hi")); // false

⚝ Try this example on CodeSandbox

11. Iterator:

The iterable is a interface that specifies that an object can be accessible if it implements a method who is key is [symbol.iterator].

const title = "ES6";
const iterateIt = title[Symbol.iterator]();

console.log(iterateIt.next().value); //output: E
console.log(iterateIt.next().value); //output: S
console.log(iterateIt.next().value); //output: 6

⚝ Try this example on CodeSandbox

# 2. VARIABLES


Q. What are global variables?

Global variables are declared outside of a function or declared with a window object for accessibility throughout the program (unless shadowed by locals). If you declare a variable without using var, even if it's inside a function, it will still be seen as global.

The var statement declares a function-scoped or globally-scoped variable, optionally initializing it to a value.

Example:

var x = 10;

if (x === 10) {
  var x = 20;

  console.log(x); // expected output: 20
}

console.log(x); // expected output: 20

Example: Declaring global variable within function

window.value = 90;

// Declaring global variable by window object
function setValue() {
  window.value = 100;
}

// Accessing global variable from other function
function getValue() {
  setValue();
  return window.value;
}

console.log(getValue()); // 100

Using Undeclared Variables:

  • In strict mode, if you attempt to use an undeclared variable, you'll get a reference error when you run your code.
  • Outside of strict mode, however, if you assign a value to a name that has not been declared with let, const, or var, you'll end up creating a new global variable. It will be global no matter how deeply nested within functions and blocks your code is, which is almost certainly not what you want, is bug-prone, and is one of the best reasons for using strict mode!
  • Global variables created in this accidental way are like global variables declared with var: they define properties of the global object. But unlike the properties defined by proper var declarations, these properties can be deleted with the delete operator.

⚝ Try this example on CodeSandbox

Q. What are template literals in es6?

Template literals help make it simple to do string interpolation, or to include variables in a string.

const person = { name: 'Tyler', age: 28 };

console.log(`Hi, my name is ${person.name} and I am ${person.age} years old!`);
// 'Hi, my name is Tyler and I am 28 years old!'

Template literals, however, preserve whatever spacing you add to them. For example, to create that same multi-line output that we created above, you can simply do:

console.log(`This is line one.
This is line two.`);
// This is line one.
// This is line two.

Another use case of template literals would be to use as a substitute for templating libraries for simple variable interpolations:

const person = { name: 'Tyler', age: 28 };

document.body.innerHTML = `
  <div>
    <p>Name: ${person.name}</p>
    <p>Name: ${person.age}</p>
  </div>
`

Q. What are the differences between variables created using let, var or const?

Variables declared using the var keyword are scoped to the function in which they are created, or if created outside of any function, to the global object. let and const are block scoped, meaning they are only accessible within the nearest set of curly braces (function, if-else block, or for-loop).

/**
 * All variables are accessible within functions.
 */
function variableScope() {

  var x = 10;
  let y = 20;
  const z = 30;

  console.log(x); // 10
  console.log(y); // 20
  console.log(z); // 30
}

console.log(x); // ReferenceError: x is not defined
console.log(y); // ReferenceError: y is not defined
console.log(z); // ReferenceError: z is not defined

variableScope();
/**
 * var declared variables are accessible anywhere in the function scope.
 */
if (true) {
  var a = 10;
  let b = 20;
  const c = 30;
}

console.log(a); // 10
console.log(b); // ReferenceError: b is not defined
console.log(c); // ReferenceError: c is not defined

var allows variables to be hoisted, meaning they can be referenced in code before they are declared. let and const will not allow this, instead throwing an error.

console.log(a); // undefined
var a = 'foo';

console.log(b); // ReferenceError: can\'t access lexical declaration 'b' before initialization
let b = 'baz';

console.log(c); // ReferenceError: can\'t access lexical declaration 'c' before initialization
const c = 'bar';

Redeclaring a variable with var will not throw an error, but 'let' and 'const' will.

var a = 'foo';
var a = 'bar';
console.log(a); // "bar"

let b = 'baz';
let b = 'qux'; // Uncaught SyntaxError: Identifier 'b' has already been declared

let and const differ in that let allows reassigning the variable's value while const does not.

// This is ok.
let a = 'foo';
a = 'bar';
console.log(a); // bar

// This causes an exception.
const b = 'baz';
b = 'qux';
console.log(b) // TypeError: Assignment to constant variable.

⚝ Try this example on CodeSandbox

Q. What is the purpose of double exclamation?

The double exclamation mark (!!) in JavaScript forces any value into its corresponding strict boolean representation (true or false). The !! (double negation) operator converts any value to its boolean equivalent:

  • First ! negates the value (converting it to a boolean)
  • Second ! negates it again, giving the true boolean representation

Example:

console.log(!!0);         // false
console.log(!!"");        // false
console.log(!!null);      // false
console.log(!!undefined); // false
console.log(!!NaN);       // false

console.log(!!1);         // true
console.log(!!"text");    // true
console.log(!!{});        // true
console.log(!![]);        // true

Note: It's commonly used to explicitly cast a value to boolean without using Boolean(value).

Q. In JavaScript, what is the difference between var x = 1 and x = 1?

The primary difference is that var x = 1 explicitly declares a function-scoped or globally-scoped variable, whereas x = 1 assigns a value to an identifier without declaring it, which can lead to global scope pollution or runtime errors depending on your code's strictness mode

var x = 1:

  • Allowed in 'strict mode'.
  • The var statement declares a function-scoped or globally-scoped variable, optionally initializing it to a value.
  • Variables declared using var inside a { } block can be accessed from outside the block.
  • Variables defined using var inside a function are not accessible (visible) from outside the function.
  • Duplicate variable declarations using var will not trigger an error, even in strict mode, and the variable will not lose its value unless another assignment is performed.

Example:

var x = 1;

if (x === 1) {
  var x = 2;

  console.log(x); // expected output: 2
}

console.log(x); // expected output: 2
var x = 5; // global
function someThing(y) {
  var x = 3; // local
  var z = x + y;
  console.log(z);
}
someThing(4); // 7
console.log(x); // 5

x = 1:

  • Not allowed in 'strict mode'.
  • Undeclared Variables like: x = 1 is accessible in: (Block scope - Function scope - Global scope)
  • Outside of strict mode, however, if you assign a value to a name that has not been declared with let, const, or var, you'll end up creating a new global variable. It will be global no matter how deeply nested within functions and blocks your code is, which is almost certainly not what you want, is bug-prone, and is one of the best reasons for using strict mode!
  • Global variables created in this accidental way are like global variables declared with var they define properties of the global object.
  • Unlike the properties defined by proper var declarations, these properties can be deleted with the delete operator.
  • Not recommended.

Example:

var x = 5; // global
function someThing(y) {
  x = 1; // still global!
  var z = x + y;
  console.log(z);
}
someThing(4) // 5
console.log(x) // 1

Example:

{
  console.log(x + y); // NaN
  var x = 1;
  var y = 2;
}
{
  console.log(x + y); // Uncaught ReferenceError: x is not defined
   x = 1;
   y = 2;
}

var x = 1x = 1
Strict mode✔❌
Block scope❌✔
Function scope✔✔
Global scope✔✔
Hoisting✔❌
Reassigning✔✔

Q. How do you assign default values to variables?

In JavaScript, you can assign default values to variables using several modern and legacy approaches, depending on the exact conditions you want to check.

1. OR operator (||)

let name = userInput || "Default";
// Uses "Default" if userInput is falsy (0, "", null, undefined, false)

2. Nullish coalescing (??) (preferred)

let name = userInput ?? "Default";
// Uses "Default" only if userInput is null or undefined (not 0 or "")

3. Destructuring defaults

const { name = "John", age = 25 } = user;
const [first = 0, second = 0] = arr;

4. Function parameter defaults

function greet(name = "World") {
  return `Hello, ${name}!`;
}
greet();        // "Hello, World!"
greet("Alice"); // "Hello, Alice!"

5. Logical OR assignment (||=)

let x = null;
x ||= "default"; // x = "default"

6. Nullish assignment (??=)

let x = 0;
x ??= "default"; // x stays 0 (not null/undefined)

Key difference between || and ??:

let count = 0;
count || 10;  // 10  — 0 is falsy, so falls back
count ?? 10;  // 0   — 0 is not null/undefined, so keeps it

Note: Use ?? when 0, "", or false are valid values.

Q. What is the precedence order between local and global variables?

Local variables always take precedence over global variables within their scope.

let x = "global";

function example() {
  let x = "local";
  console.log(x); // "local" — local shadows the global
}

example();
console.log(x); // "global" — unchanged

Scope chain — JavaScript looks up variables from innermost to outermost:

let x = "global";

function outer() {
  let x = "outer";

  function inner() {
    let x = "inner";
    console.log(x); // "inner"
  }

  inner();
  console.log(x); // "outer"
}

outer();
console.log(x); // "global"

If no local variable exists, it walks up the chain:

let x = "global";

function example() {
  console.log(x); // "global" — no local x, found in outer scope
}

Shadowing pitfall:

let x = "global";

function example() {
  console.log(x); // ReferenceError (TDZ) — let/const are hoisted but not initialized
  let x = "local";
}

Precedence order (innermost → outermost):

  1. Local (block/function) scope
  2. Enclosing/closure scopes
  3. Module scope
  4. Global scope (window / globalThis)

⚝ Try this example on CodeSandbox

Q. What is variable shadowing in javascript?

Variable shadowing occurs when a variable declared within a certain scope (decision block, method, or inner class) has the same name as a variable declared in an outer scope. This outer variable is said to be shadowed.

If there is a variable in the global scope, and you'd like to create a variable with the same name in a function. The variable in the inner scope will temporarily shadow the variable in the outer scope.

Example:

let x = "global";

function example() {
  let x = "local"; // shadows the global x
  console.log(x);  // "local"
}

example();
console.log(x); // "global" — outer x is unaffected

Block-level shadowing (let/const):

let msg = "outside";

if (true) {
  let msg = "inside"; // shadows outer msg
  console.log(msg);   // "inside"
}

console.log(msg); // "outside"

var does NOT shadow at block level (only at function level):

var msg = "outside";

if (true) {
  var msg = "inside"; // overwrites, not shadows!
  console.log(msg);   // "inside"
}

console.log(msg); // "inside" — original was overwritten!

Nested function shadowing:

let value = 1;

function outer() {
  let value = 2;        // shadows global

  function inner() {
    let value = 3;      // shadows outer
    console.log(value); // 3
  }

  inner();
  console.log(value);   // 2
}

outer();
console.log(value);     // 1

Note:

  • Shadowing does not modify the outer variable
  • let/const shadow at block level; var only at function level
  • Can cause bugs when unintentional — use distinct variable names to avoid confusion

Q. What is scope chain in javascript?

The scope chain is the mechanism JavaScript uses to resolve variable references by searching through nested scopes from innermost to outermost until the variable is found (or a ReferenceError is thrown).

Example::

let globalVar = "I'm a global variable";

function outer() {
  let outerVar = "I'm an outer variable";

  function inner() {
    let innerVar = "I'm an inner variable";
    console.log(innerVar); // "I'm an inner variable"
    console.log(outerVar); // "I'm an outer variable"
    console.log(globalVar); // "I'm a global variable"
  }

  inner();
}

outer();

Note:

  • The chain is created at function definition time (lexical scoping), not at call time
  • It only goes outward — outer scopes cannot access inner variables
  • Closures work because inner functions retain a reference to their outer scope chain

# 3. DATA TYPES


Q. What are data types in javascript?

There are eight basic data types in JavaScript.

Data TypesDescriptionExample
StringRepresents textual datalet str = 'Hi', let str2 = "Hello", let str3 = `Hello World`
NumberAn integer or a floating-point numberlet num = 3, let num2 = 3.234, let num3 = 3e-2
BigIntAn integer with arbitrary precisionlet num = 900719925124740999n, let num = 1n
BooleanAny of two values: true or falselet flag = true
undefinedA data type whose variable is not initializedlet a;
nullDenotes a null valuelet a = null;
SymbolData type whose instances are unique and immutablelet value = Symbol('hello');
Objectkey-value pairs of collection of datalet student = { };

String:

String is used to store text. In JavaScript, strings are surrounded by quotes:

  • Single quotes: 'Hello'
  • Double quotes: "Hello"
  • Backticks: `Hello`

Example::

// Strings
const firstName = "Mukul";
const lastName = "Mittal";
const result = `Name: ${firstName} ${lastName}`;

console.log(result); // Name: Mukul Mittal

console.log(typeof result); // string

Number:

Number represents integer and floating numbers (decimals and exponentials). A number type can also be +Infinity, -Infinity, and NaN (not a number).

const number1 = 3;
const number2 = 3.433;
const number3 = 3e5; // 3 * 10^5

const number4 = 3 / 0;
console.log(number4); // Infinity
console.log(typeof number4) // number

const number5 = -3 / 0;
console.log(number5); // -Infinity
console.log(typeof number5); // number

// strings can\'t be divided by numbers
const number6 = "abc" / 3;
console.log(number6); // NaN
console.log(typeof number6); // number

BigInt:

In JavaScript, Number type can only represent numbers less than (253 - 1) and more than -(253 - 1). However, if you need to use a larger number than that, you can use the BigInt data type.

A BigInt number is created by appending n to the end of an integer.

// BigInt value
const num1 = 100000000000000000n;
const num2 = 1000000000000000000n;
const num3 = 10;

// Adding two big integers
const result1 = num1 + num2;
console.log(result1); // "1100000000000000000n"
console.log(typeof result1); // bigint

// Error! BitInt and number cannot be added
const result2 = num1 + num2 + num3;
console.log(result2); // Uncaught TypeError: Cannot mix BigInt and other types
console.log(typeof result2); // Uncaught TypeError: Cannot mix BigInt and other types

Boolean:

This data type represents logical entities. Boolean represents one of two values: true or false.

const dataChecked = true;
const valueCounted = false;

undefined:

The undefined data type represents value that is not assigned. If a variable is declared but the value is not assigned, then the value of that variable will be undefined.

let name;
console.log(name); // undefined

let name = undefined;
console.log(name); // undefined

null:

In JavaScript, null is a special value that represents empty or unknown value.

const number = null;

Symbol:

A value having the data type Symbol can be referred to as a symbol value. Symbol is an immutable primitive value that is unique.

// Two symbols with the same description

const value1 = Symbol('hello');
const value2 = Symbol('hello');

let result = (value1 === value2) ? true : false;  // false;

// Note: Though value1 and value2 both contain 'hello', they are different as they are of the Symbol type.

Object:

An object is a complex data type that allows us to store collections of data.

const employee = {
    firstName: 'John',
    lastName: 'K',
    email: 'john.k@gmail.com'
};

Q. What is undefined property?

The undefined property indicates that a variable has not been assigned a value, or not declared at all. The type of undefined value is undefined too.

var user;    // Value is undefined, type is undefined
console.log(typeof(user)) //undefined

Any variable can be emptied by setting the value to undefined.

user = undefined

Q. What is difference between null vs undefined?

Null:

Null means an empty or non-existent value. Null is assigned, and explicitly means nothing.

var test = null;
console.log(test); // null

null is also an object. Interestingly, this was actually an error in the original JavaScript implementation:

console.log(typeof test); // object

Undefined:

Undefined means a variable has been declared, but the value of that variable has not yet been defined. For example:

var test2;
console.log(test2); // undefined

Unlike null, undefined is of the type undefined:

console.log(typeof test2); // undefined

Difference:

NullUndefined
It is an assignment value which indicates that variable points to no object.It is not an assignment value where a variable has been declared but has not yet been assigned a value.
Type of null is objectType of undefined is undefined
The null value is a primitive value that represents the null, empty, or non-existent reference.The undefined value is a primitive value used when a variable has not been assigned a value.
Indicates the absence of a value for a variableIndicates absence of variable itself
Converted to zero (0) while performing primitive operationsConverted to NaN while performing primitive operations

Q. What is Coercion in JavaScript?

Type coercion is the automatic or implicit conversion of values from one data type to another (such as strings to numbers). Type conversion is similar to type coercion because they both convert values from one data type to another with one key difference — type coercion is implicit whereas type conversion can be either implicit or explicit.

Example:

const value1 = '10';
const value2 = 20;

let sum = value1 + value2;

console.log(sum);

In the above example, JavaScript has coerced the 10 from a number into a string and then concatenated the two values together, resulting in a string of 1020. JavaScript had a choice between a string or a number and decided to use a string.

// Example of explicit coercion
const value1 = '10';
const value2 = 20;

let sum = Number(value1) + value2;

console.log(sum);

# 4. OPERATORS


Q. What are various operators supported by javascript?

An operator is capable of manipulating (mathematical and logical computations) a certain value or operand. There are various operators supported by JavaScript as below,

Arithmetic Operators:

Arithmetic operators are used to perform mathematical operations between numeric operands.

OperatorsDescriptionExample ( say let x = 10, y = 20;)
+Adds two numeric operands.x + y
-Subtract right operand from left operandy - x
*Multiply two numeric operands.x * y
/Divide left operand by right operand.y / x
%Modulus operator. Returns remainder of two operands.x % 2
++Increment operator. Increase operand value by one.x++
--Decrement operator. Decrease value by one.x--

Comparison Operators:

JavaScript provides comparison operators that compare two operands and return a boolean value true or false.

OperatorsDescription
==Compares the equality of two operands without considering type.
===Compares equality of two operands with type.
!=Compares inequality of two operands.
>Returns a boolean value true if the left-side value is greater than the right-side value; otherwise, returns false.
<Returns a boolean value true if the left-side value is less than the right-side value; otherwise, returns false.
>=Returns a boolean value true if the left-side value is greater than or equal to the right-side value; otherwise, returns false.
<=Returns a boolean value true if the left-side value is less than or equal to the right-side value; otherwise, returns false.

Logical Operators:

The logical operators are used to combine two or more conditions.

1. && - is known as AND operator. It checks whether two operands are non-zero or not (0, false, undefined, null or "" are considered as zero). It returns 1 if they are non-zero; otherwise, returns 0

2. || - is known as OR operator. It checks whether any one of the two operands is non-zero or not (0, false, undefined, null or "" is considered as zero). It returns 1 if any one of of them is non-zero; otherwise, returns 0.

3. ! - is known as NOT operator. It reverses the boolean result of the operand (or condition). !false returns true, and !true returns false.

Assignment Operators:

The assignment operators to assign values to variables with less key strokes.

OperatorsDescription
=Assigns right operand value to the left operand.
+=Sums up left and right operand values and assigns the result to the left operand.
-=Subtract right operand value from the left operand value and assigns the result to the left operand.
*=Multiply left and right operand values and assigns the result to the left operand.
/=Divide left operand value by right operand value and assign the result to the left operand.
%=Get the modulus of left operand divide by right operand and assign resulted modulus to the left operand.

Q. What are the bitwise operators available in javascript?

Below are the list of bit-wise logical operators used in JavaScript

OperatorUsageDescription
Bitwise ANDa & bReturns a one in each bit position for which the corresponding bits of both operands are ones.
Bitwise ORab
Bitwise XORa ^ bReturns a one in each bit position for which the corresponding bits of either but not both operands are ones.
Bitwise NOT~ aInverts the bits of its operand.
Left shifta << bShifts a in binary representation b (< 32) bits to the left, shifting in zeroes from the right.
Sign-propagating right shifta >> bShifts a in binary representation b (< 32) bits to the right, discarding bits shifted off.
Zero-fill right shifta >>> bShifts a in binary representation b (< 32) bits to the right, discarding bits shifted off, and shifting in zeroes from the left.

Examples:

OperationResultSame asResult
5 & 110101 & 00010001
`51`50101
~ 5-6~01011010
5 << 1100101 << 11010
5 ^ 140101 ^ 00010100
5 >> 120101 >> 10010
5 >>> 120101 >>> 10010

⚝ Try this example on CodeSandbox

Q. What is the difference between == and === operators?

JavaScript provides both strict (===, !==) and type-converting (==, !=) equality comparison. The strict operators takes type of variable in consideration, while non-strict operators make type correction/conversion based upon values of variables. The strict operators follow the below conditions for different types,

  1. Two strings are strictly equal when they have the same sequence of characters, same length, and same characters in corresponding positions.
  2. Two numbers are strictly equal when they are numerically equal. i.e, Having the same number value. There are two special cases in this,
    1. NaN is not equal to anything, including NaN.
    2. Positive and negative zeros are equal to one another.
  3. Two Boolean operands are strictly equal if both are true or both are false.
  4. Two objects are strictly equal if they refer to the same Object.
  5. Null and Undefined types are not equal with ===, but equal with ==. i.e, null === undefined --> false but null == undefined --> true

Example:

0 == false // true
0 === false // false
1 == "1" // true
1 === "1" // false
null == undefined // true
null === undefined // false
"0" == false // true
"0" === false // false
[] === [] // false, refer different objects in memory
{} === {} // false, refer different objects in memory

⚝ Try this example on CodeSandbox

Q. What is typeof operator?

In JavaScript, the typeof operator returns the data type of its operand in the form of a string. The operand can be any object, function, or variable.

Example:

console.log(typeof undeclaredVariable); // "undefined"

let a;
console.log(typeof a); // "undefined"

const b = "Hello World";
console.log(typeof b); // "string"

const c = 42;
console.log(typeof c); // "number"

const d = 3.1415;
console.log(typeof d); // "number"

const e = true;
console.log(typeof e); // "boolean"

const f = null;
console.log(typeof f); // "object"

const g = undefined;
console.log(typeof g); // "undefined"

const h = { b: "c" };
console.log(typeof h); // "object"

const i = function () {
  return 10;
};

console.log(typeof i); // "function"

⚝ Try this example on CodeSandbox

Q. What is an Unary operator?

The unary(+) operator is used to convert a variable to a number. If the variable cannot be converted, it will still become a number but with the value NaN.

Example:

var x = "100";
var y = +x;
console.log(typeof x, typeof y); // string, number

var a = "Hello";
var b = +a;
console.log(typeof a, typeof b, b); // string, number, NaN

⚝ Try this example on CodeSandbox

Q. What is the purpose of delete operator?

The delete keyword is used to delete the property as well as its value.

Example:

const user = { name: "Sadhika Chaudhuri", age: 24 };
delete user.age;

console.log(user); // { name: "Sadhika Chaudhuri" }

Q. What is a conditional operator in javascript?

The conditional (ternary) operator is the only JavaScript operator that takes three operands which acts as a shortcut for if statement.

Syntax:

<condition> ? <value1> : <value2>;

Example:

const isAuthenticated = false;

console.log(isAuthenticated ? 'Hello, welcome' : 'Sorry, you are not authenticated');

Q. Can you apply chaining on conditional operator?

Yes, you can apply chaining on conditional operator similar to if … else if … else if … else chain.

Example:

function getValue(someParam) {
  return condition1 ? value1
    : condition2 ? value2
    : condition3 ? value3
    : value4;
}

// The above conditional operator is equivalent to:
function getValue(someParam) {
  if (condition1) {
    return value1;
  } else if (condition2) {
    return value2;
  } else if (condition3) {
    return value3;
  } else {
    return value4;
  }
}

Q. What is the difference between typeof and instanceof operator?

The typeof operator checks if a value has type of primitive type which can be one of boolean, function, object, number, string, undefined, symbol, or bigint.

Example:

const x = "Hello World";
const y = new String("Hello World");

typeof x; // returns 'string'
typeof y; // returns 'object'

The instanceof is a binary operator, accepting an object and a constructor. It returns a boolean indicating whether or not the object has the given constructor in its prototype chain.

const a = "Hello World";
const b = new String("Hello World");

a instanceof String; // returns false
b instanceof String; // returns true

⚝ Try this example on CodeSandbox

Q. What is the output of below spread operator array?

[...'Hello']

Output: ['H', 'e', 'l', 'l', 'o']

Explanation: The string is an iterable type and the spread operator with in an array maps every character of an iterable to one element. Hence, each character of a string becomes an element within an Array.

Q. What is the nullish coalescing operator (??) in JavaScript?

The nullish coalescing operator (??) returns the right-hand side operand when the left-hand side operand is null or undefined. Otherwise, it returns the left-hand side operand. Unlike ||, it does not treat 0, false, or "" as falsy.

Syntax:

leftExpr ?? rightExpr

Example:

console.log(null ?? 'default');      // 'default'
console.log(undefined ?? 'default'); // 'default'
console.log(0 ?? 'default');         // 0  (0 is NOT null/undefined)
console.log('' ?? 'default');        // '' (empty string is NOT null/undefined)
console.log(false ?? 'default');     // false

// Practical use: provide a fallback for a missing config value
const config = { timeout: 0 };
const timeout = config.timeout ?? 3000;
console.log(timeout); // 0 (not 3000, because 0 is a valid value)

Nullish coalescing assignment (??=):

let user = { name: null };
user.name ??= 'Anonymous';
console.log(user.name); // 'Anonymous'

Q. What are logical assignment operators in JavaScript?

Logical assignment operators (ES2021) combine a logical operator with assignment. There are three:

OperatorEquivalent toDescription
&&=a && (a = b)Assigns b to a only if a is truthy
||=a || (a = b)Assigns b to a only if a is falsy
??=a ?? (a = b)Assigns b to a only if a is null or undefined

Example:

// &&= (AND assignment)
let a = 1;
a &&= 2;
console.log(a); // 2 (a was truthy, so assigned)

let b = 0;
b &&= 2;
console.log(b); // 0 (b was falsy, not assigned)

// ||= (OR assignment)
let c = null;
c ||= 'default';
console.log(c); // 'default' (c was falsy)

let d = 'existing';
d ||= 'default';
console.log(d); // 'existing' (d was truthy, not reassigned)

// ??= (Nullish assignment)
let e = null;
e ??= 'fallback';
console.log(e); // 'fallback'

let f = 0;
f ??= 'fallback';
console.log(f); // 0 (0 is not null/undefined)

# 5. NUMBERS


Q. How do you generate random integers?

The Math.random() function returns a floating-point, pseudo-random number in the range 0 to less than 1 (inclusive of 0, but not 1). For example, if you want generate random integers between 1 to 100, the multiplication factor should be 100,

// Example 01:
Math.random(); // returns a random integer between 0 to 1

// Example 02:
Math.floor(Math.random() * 100) + 1; // returns a random integer from 1 to 100

// Example 03:
function getRandomNumber(max) {
  return Math.floor(Math.random() * max) + 1;
}

console.log(getRandomNumber(10)); // returns a random integer from 1 to 10

Random integer between min and max (inclusive):

function randomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

console.log(randomInt(1, 6));   // simulates a dice roll: 1–6

⚝ Try this example on CodeSandbox

Q. What is isNaN?

The isNaN() function determines whether a value is NaN ( Not a Number ) or not. This function returns true if the value equates to NaN. The isNaN() method converts the value to a number before testing it.

Example:

isNaN('Hello') // true

isNaN('100') // false

typeof NaN // Number

Number.isNaN('Hello'); // false

⚝ Try this example on CodeSandbox

Q. What is the purpose of isFinite function?

The global isFinite() function determines whether the passed value is a finite number. It returns false if the value is +infinity, -infinity, or NaN (Not-a-Number), otherwise it returns true.

isFinite(Infinity);  // false
isFinite(NaN);       // false
isFinite(-Infinity); // false

isFinite(100);  // true
isFinite(1/0); // false

Number.isFinite(0 / 0); // false
Number.isFinite(null); // false
Number.isFinite("123") // false

⚝ Try this example on CodeSandbox

Q. Explain NEGATIVE_INFINITY in JavaScript?

The Number.NEGATIVE_INFINITY property represents the negative Infinity value.

Syntax:

Number.NEGATIVE_INFINITY
  • Negative infinity is a number in javascript, which is derived by 'dividing negative number by zero'.
  • A number object needs not to be created to access this static property.
  • The value of negative infinity is the same as the negative value of the infinity property of the global object.
/**
 * NEGATIVE_INFINITY
 */

console.log(-10/0); // -Infinity
console.log(Number.NEGATIVE_INFINITY); // -Infinity
console.log(Number.MAX_VALUE + Number.MAX_VALUE); // Infinity
console.log(-2 * Number.MAX_VALUE); // -Infinity

console.log("Math.pow(10, 1000): " + Math.pow(10, 1000)); // Infinity 
console.log("Math.log(0): " + Math.log(0)); // -Infinity

console.log(Number.NEGATIVE_INFINITY === -2 * Number.MAX_VALUE); // true

⚝ Try this example on CodeSandbox

# 6. STRING


Q. What is the difference between slice and splice?

1. slice():

The slice() method returns a new array with a copied slice from the original array. The first optional argument is the beginning index and the second optional argument is the ending index (non-inclusive).

Example:

let languages = [ "JavaScript", "Python", "Java", "PHP" ];

languages.slice(1,3); // ["Python", "Java"]
languages.slice(2); // (from index 2 until the end of the array).
// ["Java", "PHP"]

console.log(languages); // the original array is not mutated.
// [ "JavaScript", "Python", "Java", "PHP" ]

2. splice():

The splice() method changes the content of the array in place and can be used to add or remove items from the array. When only one argument is provided, all the items after the provided starting index are removed from the array.

Example:

let numbers = [10, 20, 30];

numbers.splice(2, 1, 40, 50); // returns removed array:[30]

console.log(numbers); // Original array is mutated.
// returns: [10, 20, 40, 50]

Difference:

SliceSplice
Doesn't modify the original array(immutable)Modifies the original array(mutable)
Returns the subset of original arrayReturns the deleted elements as array
Used to pick the elements from arrayUsed to insert or delete elements to/from array

⚝ Try this example on CodeSandbox

Q. How do you check whether a string contains a substring?

There are 3 fastest ways to check whether a string contains a substring or not,

1. Using RegEx:

The regular expression test() method checks if a match exists in a string. This method returns true if it finds a match, otherwise, it returns false.

let str = "JavaScript, Node.js, Express.js, React.js, MongoDB";
let exp1 = /MongoDB/g;
let exp2 = /Ajax/;

exp1.test(str); // true
exp2.test(str); // false

2. Using indexOf:

The indexOf() method is case-sensitive and accepts two parameters. The first parameter is the substring to search for, and the second optional parameter is the index to start the search from (default index is 0).

let str = "JavaScript, Node.js, Express.js, React.js, MongoDB";

str.indexOf('MongoDB') !== -1 // true
str.indexOf('PHP') !== -1 // false
str.indexOf('Node', 5) !== -1 // true

3. Using includes: (Modern Standard)

The includes() is also case-sensitive and accepts an optional second parameter, an integer which indicates the position where to start searching for.

let str = "JavaScript, Node.js, Express.js, React.js, MongoDB";

str.includes('MongoDB') // true
str.includes('PHP') // false
str.includes('Node', 5) //true

Use Cases:

Use CaseMethodExample
Simple readability checkincludes()Check if a product description contains a keyword
Legacy browser supportindexOf()Check if URL contains a query param
Pattern-based searchRegEx test()Check if input matches a specific format
// 1. Search bar — check if item name contains user query
const query = "react";
const items = ["React.js Tutorial", "Node.js Guide", "React Native Basics"];
const results = items.filter(item => item.toLowerCase().includes(query.toLowerCase()));
// ["React.js Tutorial", "React Native Basics"]

// 2. URL parsing — check if a query param exists
const url = "https://example.com/search?q=javascript&page=2";
const hasQuery = url.indexOf('?q=') !== -1; // true

// 3. Input validation — check if email contains '@' and '.'
const email = "user@example.com";
const isValidEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); // true

⚝ Try this example on CodeSandbox

Q. How do you trim a string in javascript?

The trim() method removes whitespace from both sides of a string. JavaScript provides 3 simple functions on how to trim strings.

1. string.trim():

The string.trim() removes sequences of whitespaces and line terminators from both the start and the end of the string.

const name = "  Karan Talwar  ";
console.log(name.trim()); // => 'Karan Talwar'

const phoneNumber = "\t  80-555-123\n ";
console.log(phoneNumber.trim()); // => '80-555-123'

2. string.trimStart():

The string.trimStart() removes sequences of whitespaces and line terminators only from the start of the string.

const name = "   Karan Talwar  ";
console.log(name.trimStart()); // => "Karan Talwar  "

const phoneNumber = "\t  80-555-123\n ";
console.log(phoneNumber.trimStart()); // => "80-555-123 "

3. string.trimEnd():

The string.trimEnd() removes sequences of whitespaces and line terminators only from the end of the string.

const name = "  Karan Talwar ";
console.log(name.trimEnd()); // => " Karan Talwar"

const phoneNumber = "\t  80-555-123\n ";
console.log(phoneNumber.trimEnd()); // => " 80-555-123"

⚝ Try this example on CodeSandbox

Q. What is eval function in javascript?

The eval() function evaluates JavaScript code represented as a string. The string can be a JavaScript expression, variable, statement, or sequence of statements.

console.log(eval('10 + 20')); // 30

let x = 10;
let y = 20;
let z = '50';
eval('x + y + 1'); // returns 31
eval(z);           // returns 50

If the argument of eval() is not a string, eval() returns the argument unchanged. In the following example, the String constructor is specified and eval() returns a String object rather than evaluating the string.

eval(new String('10 + 20')); // returns a String object containing "10 + 20"
eval('10 + 20');             // returns 30


// work around
let expression = new String('10 + 20');
eval(expression.toString()); // returns 30

Warning: Executing JavaScript from a string is an enormous security risk. It is far too easy for a bad actor to run arbitrary code when you use eval().

⚝ Try this example on CodeSandbox

Q. How do you check if a string starts with another string?

You can use String.prototype.startsWith() method to check if a string starts with another string or not. It is fully supported in all modern browsers.

let str = "Hello World";

console.log(str.startsWith("Hello")); // true
console.log(str.startsWith("World")); // false

⚝ Try this example on CodeSandbox

Q. What are replaceAll(), padStart() and padEnd() string methods?

1. String.prototype.replaceAll():

The replaceAll() method returns a new string with all matches of a pattern replaced by a replacement. Unlike replace(), it replaces every occurrence without needing a global regex flag.

const text = 'I like cats. Cats are cute cats.';
console.log(text.replace('cats', 'dogs'));    // 'I like dogs. Cats are cute cats.' (only first)
console.log(text.replaceAll('cats', 'dogs')); // 'I like dogs. Cats are cute dogs.'

2. String.prototype.padStart():

The padStart() method pads the current string with another string from the start until it reaches the given length.

console.log('5'.padStart(3, '0'));    // '005'
console.log('hello'.padStart(8));     // '   hello' (default pad is space)
console.log('42'.padStart(5, '*'));   // '***42'

3. String.prototype.padEnd():

The padEnd() method pads the current string with another string from the end until it reaches the given length.

console.log('5'.padEnd(3, '0'));    // '500'
console.log('hello'.padEnd(8));     // 'hello   '
console.log('42'.padEnd(5, '-'));   // '42---'

# 7. ARRAY


Q. Explain arrays in JavaScript?

JavaScript array is an object that represents a collection of similar type of elements. It can holds values (of any type) not particularly in named properties/keys, but rather in numerically indexed positions.

Syntax:

const array_name = [item-1, item-2, item-3, ...];    

Example 01: Creating an array

// array of numbers
const numbers = [10, 20, 30, 40, 50];

// using new keyword
const numbers = new Array(10, 20, 30, 40, 50);

// array of strings
let fruits = ["Apple", "Orange", "Plum", "Mango"];

// array of character
const numbers = Array.from("abc"); // ["a", "b", "c"]

// array of numbers
const numbers = Array.of(10, 20, 30); // [10, 20, 30]

Common Methods

CategoryMethods
Add/Removepush, pop, shift, unshift, splice
Extractslice, flat, flatMap
Searchfind, findIndex, indexOf, includes
IterateforEach, map, filter, reduce
Sort/Ordersort, reverse
Combineconcat, join

Example 02: Accessing array elements

const numbers = [10, 20, 30, 40, 50];

// Transforming
console.log(numbers.map(n => n * 2)); // [20, 40, 60, 80, 100]

// Filtering
console.log(numbers.filter(n => n % 2 === 0)); // [10, 20, 30, 40, 50]

// Reducing
console.log(numbers.reduce((sum, n) => sum + n, 0)); // 150

// Searching
console.log(numbers.find(n => n > 30));  // 40
console.log(numbers.includes(30));  // true

Example 03: Adding new array elements

let fruits = ["Apple", "Orange", "Plum", "Mango"];

fruits.push("Grapes");  // Adds a new element (Grapes) to fruits

⚝ Try this example on CodeSandbox

Q. What are associative arrays in javascript?

JavaScript does not have built-in associative arrays in the way languages like PHP or literal dictionary structures do. Instead, JavaScript uses regular Objects or Maps to handle key-value pairs (associative behavior).

Associative arrays are basically objects in JavaScript where indexes are replaced by user-defined keys. They do not have a length property like a normal array and cannot be traversed using a normal for loop.

Syntax:

const array_name = { key1: 'value1', key2: 'value2', key3: 'value3' }   

Example: Plain Object (most common)

const employee = {
  id: 12345,
  name: "Sakshi Memon",
  email: "sakshi.memon@email.com"
};

// Accessing employee elements
console.log(employee.id); // 12345
console.log(employee.name); // Sakshi Memon

// Array Length 
console.log(Object.keys(employee).length); // 3

// Retrieve the elements
for (let key in employee) {
  console.log(key + " = " + employee[key]);
}

// Output
id = 12345 
name = Sakshi Memon 
email = sakshi.memon@email.com 

Example: Map (true associative structure)

const map = new Map();
map.set("name", "Alice");
map.set(42, "answer");        // keys can be any type
map.set({ id: 1 }, "object key");

map.get("name");  // "Alice"
map.has(42);      // true
map.size;         // 3

⚝ Try this example on CodeSandbox

Q. How to calculate the length of the associative array?

Method 1: Using Object.keys().length

const employee = {
  id: 12345,
  name: "Sakshi Memon",
  email: "sakshi.memon@email.com"
};

console.log(Object.keys(employee).length); // 3

Method 2: Using Object.hasOwnProperty()

function getLength(object) {
  let count = 0;
  for (let key in object) {
    // hasOwnProperty method check own property of object
    if (object.hasOwnProperty(key)) count++;
  }
  return count;
}

console.log(getLength(employee)); // 3

Method 3: Using Object.getOwnPropertyNames()

const employee = {
  id: 12345,
  name: "Sakshi Memon",
  email: "sakshi.memon@email.com"
};

Object.getOwnPropertyNames(employee).length; // 3

⚝ Try this example on CodeSandbox

Q. What is the difference between Array and Array of Objects in JavaScript?

Objects represent a special data type that is mutable and can be used to store a collection of data (rather than just a single value). Arrays are a special type of variable that is also mutable and can also be used to store a list of values.

Example: Arrays

const numbers = [10, 20, 30];

// Iterating through loop
for (let i = 0; i < numbers.length; i++) {
  console.log(numbers[i]);
}

// Pop an element from array
numbers.pop();
console.log("after pop(): " + numbers);

Example: Array of Objects

const employees = [
  { id: 101, name: "Sakshi Memon", email: "sakshi.memon@email.com" },
  { id: 102, name: "Subhash Shukla", email: "subhash.shukla@email.com" },
  { id: 103, name: "Mohini Karpe", email: "mohini.karpe@email.com" }
];

// Using DOT notation
console.log(employees[0].name);

// Using delete keyword
delete employees[0];

// Iterating using for..in loop
for (let key in employees) {
  console.log(employees[key]);
}

Difference:

S.No.ArrayArray of objects
1.Arrays are best to use when the elements are numbers.Objects are best to use when the elements strings
3.The elements can be manipulated using [].The properties can be manipulated using both . ( DOT ) notation and [].
4.The elements can be popped out of an array using the pop() function.The keys or properties can be deleted by using the delete keyword.
5.Iterating through an array is possible using For loop, For..in, For..of, and ForEach().Iterating through an array of objects is possible using For..in, For..of, and ForEach().

⚝ Try this example on CodeSandbox

Q. Explain array methods [ join(), pop(), push(), shift(), unshift(), concat(), map(), filter(), reduce(), reduceRight(), every(), some(), indexOf(), lastIndexOf(), find(), findIndex(), includes() ]

1. array.join():

The join() method creates and returns a new string by concatenating all of the elements in an array (or an array-like object), separated by commas or a specified separator string. If the array has only one item, then that item will be returned without using the separator.

var elements = ['Fire', 'Air', 'Water'];

console.log(elements.join()); // Output: "Fire,Air,Water"
console.log(elements.join('')); // Output: "FireAirWater"
console.log(elements.join('-')); // Output: "Fire-Air-Water"

2. array.pop():

The pop() method removes the last element from an array and returns that element. This method changes the length of the array.

var plants = ['broccoli', 'cauliflower', 'kale'];

console.log(plants.pop()); // Output: "kale"
console.log(plants); // Output: Array ["broccoli", "cauliflower"]
console.log(plants.pop()); // Output: "cauliflower"
console.log(plants.pop()); // Output: "broccoli"
console.log(plants.pop()); // Output: "undefined"

3. array.push():

The push() method adds one or more elements to the end of an array and returns the new length of the array.

const animals = ['pigs', 'goats', 'sheep'];

const count = animals.push('cows');
console.log(count); // Output: 4
console.log(animals); // Output: Array ["pigs", "goats", "sheep", "cows"]

4. array.shift():

The shift() method removes the first element from an array and returns that removed element. This method changes the length of the array.

var fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits.shift();
console.log(fruits) // Output: Array ["Orange", "Apple", "Mango"]

5. array.unshift():

The unshift() method adds one or more elements to the beginning of an array and returns the new length of the array.

var fruits = ["Banana", "Orange", "Apple"];
fruits.unshift("Mango","Pineapple");
console.log(fruits); // Output: Array ["Mango", "Pineapple", "Banana", "Orange", "Apple"]

6. array.concat():

The concat() method is used to merge two or more arrays. This method does not change the existing arrays, but instead returns a new array.

const array1 = ['a', 'b', 'c'];
const array2 = ['d', 'e', 'f'];

console.log(array1.concat(array2)); // Output: Array ["a", "b", "c", "d", "e", "f"]

7. array.map():

The map() method creates a new array with the results of calling a provided function on every element in the calling array.

var array1 = [1, 4, 9, 16];

// pass a function to map
const map1 = array1.map(x => x * 2); 

console.log(map1); // Output: Array [2, 8, 18, 32]

8. array.filter():

The filter() method creates a new array with all elements that pass the test implemented by the provided function.

var words = ['spray', 'limit', 'elite', 'exuberant', 'destruction'];

const result = words.filter(word => word.length > 6);

console.log(result); // Output: Array ["exuberant", "destruction"]

9. array.reduce():

The reduce() method executes a reducer function (that you provide) on each element of the array, resulting in a single output value.

const array1 = [1, 2, 3, 4];
const reducer = (accumulator, currentValue) => accumulator + currentValue;

console.log(array1.reduce(reducer)); // Output: 10
console.log(array1.reduce(reducer, 5)); // Output: 15

10. array.reduceRight():

The reduceRight() method applies a function against an accumulator and each value of the array (from right-to-left) to reduce it to a single value.

const array1 = [[0, 1], [2, 3], [4, 5]].reduceRight(
  (accumulator, currentValue) => accumulator.concat(currentValue)
);

console.log(array1); // Output: Array [4, 5, 2, 3, 0, 1]

11. array.every():

The every() method tests whether all elements in the array pass the test implemented by the provided function. It returns a Boolean value.

function isBelowThreshold(currentValue) {
  return currentValue < 40;
}

var array1 = [1, 30, 39, 29, 10, 13];
console.log(array1.every(isBelowThreshold)); // Output: true

12. array.some():

The some() method tests whether at least one element in the array passes the test implemented by the provided function. It returns a Boolean value.

var array = [1, 2, 3, 4, 5];

var even = function(element) {
  // checks whether an element is even
  return element % 2 === 0;
};

console.log(array.some(even)); // Output: true

13. array.indexOf():

The indexOf() method returns the first index at which a given element can be found in the array, or -1 if it is not present.

var beasts = ['ant', 'bison', 'camel'];

console.log(beasts.indexOf('camel')); // Output: 2
console.log(beasts.indexOf('giraffe')); // Output: -1

14. array.lastIndexOf():

The lastIndexOf() method returns the index within the calling String object of the last occurrence of the specified value, searching backwards from fromIndex. Returns -1 if the value is not found.

var paragraph = 'The quick brown fox jumps over the lazy dog. If the dog barked, was it really lazy?';

var searchTerm = 'dog';

console.log('The index of the first "' + searchTerm + '" from the end is ' + paragraph.lastIndexOf(searchTerm));
// Output: "The index of the first "dog" from the end is 52"

15. array.find():

The find() method returns the value of the first element in the provided array that satisfies the provided testing function.

var array1 = [5, 12, 8, 130, 44];

var found = array1.find(function(element) {
  return element > 100;
});

console.log(found); // Output: 130

16. array.findIndex():

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

var array1 = [5, 12, 8, 130, 44];

function isLargeNumber(element) {
  return element > 20;
}

console.log(array1.findIndex(isLargeNumber)); // Output: 3

17. array.includes():

The includes() method determines whether an array includes a certain value among its entries, returning true or false as appropriate.

var array1 = [1, 2, 3];
console.log(array1.includes(2)); // Output: true

var pets = ['cat', 'dog', 'bat'];
console.log(pets.includes('at')); // Output: false

18. array.at():

The at() method (ES2022) takes an integer and returns the item at that index. Negative integers count back from the last item.

const fruits = ['Apple', 'Orange', 'Banana', 'Mango'];
console.log(fruits.at(0));  // Output: Apple
console.log(fruits.at(-1)); // Output: Mango (last element)
console.log(fruits.at(-2)); // Output: Banana

19. array.flat():

The flat() method creates a new array with all sub-array elements concatenated into it recursively up to the specified depth.

const nested = [1, [2, 3], [4, [5, 6]]];
console.log(nested.flat());    // Output: [1, 2, 3, 4, [5, 6]]
console.log(nested.flat(2));   // Output: [1, 2, 3, 4, 5, 6]
console.log(nested.flat(Infinity)); // Output: [1, 2, 3, 4, 5, 6]

20. array.flatMap():

The flatMap() method maps each element using a mapping function, then flattens the result into a new array (one level deep).

const sentences = ['Hello World', 'Foo Bar'];
console.log(sentences.flatMap(s => s.split(' '))); // Output: ['Hello', 'World', 'Foo', 'Bar']

21. array.findLast():

The findLast() method (ES2023) iterates the array in reverse and returns the value of the first element that satisfies the provided testing function.

const numbers = [5, 12, 8, 130, 44];
console.log(numbers.findLast(n => n > 10)); // Output: 44

22. array.findLastIndex():

The findLastIndex() method (ES2023) iterates the array in reverse and returns the index of the first element that satisfies the provided testing function.

const numbers = [5, 12, 8, 130, 44];
console.log(numbers.findLastIndex(n => n > 10)); // Output: 4

Q. What are the benefits of using spread syntax and how is it different from rest syntax?

Spread operator or Spread Syntax allow us to expand the arrays and objects into elements in the case of an array and key-value pairs in the case of an object.

Example:

function sum(x, y, z) {
  return x + y + z;
}

const numbers = [1, 2, 3];
console.log(sum(...numbers));

// ES-5 way
console.log(sum.apply(null, numbers));

Example: Merge arrays

const newBrands = ["Tesla", "Mahindra"];
const brands = ["Ford", "Honda", ...newBrands, "BMW"];

console.log(brands);

Example: Copy array/object

let obj = { a: 10, b: 20, c: 30 };

// spread the object into a list of parameters
let objCopy = { ...obj };

// add new
obj.d = 40;

console.log(JSON.stringify(obj)); // { "a":10, "b":20, "c":30, "d":40 }
console.log(JSON.stringify(objCopy)); // { "a":10, "b":20, "c":30 }

Difference:

The main difference between rest and spread is that the rest operator puts the rest of some specific user-supplied values into a JavaScript array. But the spread syntax expands iterables into individual elements.

Spread SyntaxRest Syntax
Spread operator as its name suggests it spreads or expands the content of the given element.Rest Syntax is just the opposite of spread syntax it collects the data and stores that data in a variable which we can use further in our code.
It expands an Array in form of elements, while in key-value pairs in the case of Objects.It collects the data in the developer's desired format.
You may or may not use the strict mode inside the function containing the spread operator.You can not use the strict mode inside function containing the rest operator.
It will overwrite the identical properties inside two objects and replace the former with the latter.It simply collects all properties and wraps them inside a container.

⚝ Try this example on CodeSandbox

Q. What is the difference between for..in and for..of?

Both for..of and for..in statements iterate over lists; the values iterated on are different though, for..in returns a list of keys on the object being iterated, whereas for..of returns a list of values of the numeric properties of the object being iterated.

  • for in: iterates over all enumerable properties of an object that are keyed by strings.
  • for of: iterates over the values of an iterable objects. including: built-in String, Array, array-like objects (e.g., arguments or NodeList), TypedArray, Map, Set, and user-defined iterables.

Example:

// for..in
const list = [10, 20, 30];

for (let key in list) {
  console.log(key); // "0", "1", "2",
}

// for..of
for (let value of list) {
  console.log(value); // "10", "20", "30"
}

// Works on any iterable: arrays, strings, Map, Set
for (let char of "hello") {
  console.log(char); // "h", "e", "l", "l", "o"
}

⚝ Try this example on CodeSandbox

Q. When to use reduce(), map(), foreach() and filter() in JavaScript?

1. forEach():

It takes a callback function and run that callback function on each element of array one by one. Basically forEach works as a traditional for loop looping over the array and providing array elements to do operations on them.

let numbers = [10, 20, 30];

numbers.forEach(function (number, index) {
  console.log(number + " comes at " + index);
});

// Output
10 comes at 0
20 comes at 1
30 comes at 2

2. filter():

The main difference between forEach() and filter() is that forEach just loop over the array and executes the callback but filter executes the callback and check its return value. If the value is true element remains in the resulting array but if the return value is false the element will be removed for the resulting array.

Note: filter does not update the existing array it will return a new filtered array every time.

let numbers = [10, 20, 30];

let result = numbers.filter(function (number) {
  return number !== 20;
});

console.log(result);

// Output
[10, 30]

3. map():

map() like filter() & forEach() takes a callback and run it against every element on the array but whats makes it unique is it generate a new array based on your existing array.

Like filter(), map() also returns an array. The provided callback to map modifies the array elements and save them into the new array upon completion that array get returned as the mapped array.

let numbers = [10, 20, 30];

let mapped = numbers.map(function (number) {
  return number * 10;
});

console.log(mapped);

// Output
[100, 200, 300]

4. reduce():

reduce() method of the array object is used to reduce the array to one single value.

let numbers = [10, 20, 30];

let sum = numbers.reduce(function (sum, number) {
  return sum + number;
});

console.log(sum); // Output: 60

Key Rules

  • Need a new transformed array? → map()
  • Need a subset? → filter()
  • Need a single output (sum, object, string)? → reduce()
  • Need no return value (side effects)? → forEach()
  • Avoid forEach() when you need a result — use map()/filter()/reduce() instead

⚝ Try this example on CodeSandbox

Q. How do you define JSON arrays?

JSON is an acronym for JavaScript Object Notation, and is "an open standard data interchange format".

JSON array represents ordered list of values. JSON array can store multiple values. It can store string, number, boolean or object in JSON array.

// Empty JSON array
const empty = [ ];


// JSON Array of Numbers
const numbers = [12, 34, 56, 43, 95];


// JSON Array of Objects
{
 "employees": [
   { "name": "Kabir Dixit", "email": "kabir.dixit@gmail.com", "age": 23 },
   { "name": "Mukta Bhagat", "email": "mukta.bhagat@gmail.com", "age": 28 },
   { "name": "Sakshi Ramakrishnan", "email": "sakshi.ramakrishnan@gmail.com", "age": 33 }
  ]
}

// access array values
console.log(employees[0].name) // Kabir Dixit

Q. How to validate JSON Object in javascript?

JSON.parse() function will use string and converts to JSON object and if it parses invalidate JSON data, it throws an exception ( Uncaught SyntaxError: Unexpected string in JSON ).

function isValidJson(json) {
  try {
    JSON.parse(json);
    return true;
  } catch (e) {
    return false;
  }
}

console.log(isValidJson("{}")); // true
console.log(isValidJson("abc")); // false

⚝ Try this example on CodeSandbox

Q. What is the purpose JSON stringify?

When sending data to a web server, the data has to be in a string format. The JSON.stringify() method converts a JavaScript object or value to a JSON string format.

const user = {'name': 'Shashi Meda', 'email': 'shashi.meda@email.com', 'age': 28}

console.log(JSON.stringify(user)); // {"name":"Shashi Meda","email":"shashi.meda@email.com","age":28}

Q. How do you parse JSON string?

When receiving the data from a web server, the data is always in a string format. But you can convert this string value to javascript object using JSON.parse() method.

const user = '{"name": "Shashi Meda", "email": "shashi.meda@email.com", "age": 28}'

console.log(JSON.parse(user));// {'name': 'Shashi Meda', 'email': 'shashi.meda@email.com', 'age': 28}

Q. What is the purpose of compare function while sorting arrays?

The purpose of the compare function is to define an alternative sort order. When the sort() function compares two values, it sends the values to the compare function, and sorts the values according to the returned (negative, zero, positive) value.

If omitted, the array elements are converted to strings, then sorted according to each character's Unicode code point value.

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

numbers.sort((a, b) => b - a);
console.log(numbers); // [5, 4, 3, 2, 1]

⚝ Try this example on CodeSandbox

Q. Can you describe the main difference between a .forEach loop and a .map() loop and why you would pick one versus the other?

To understand the differences between the two, Let us look at what each function does.

1. Array.forEach():

  • Iterates through the elements in an array.
  • Executes a callback for each element.
  • Does not return a value.
const numbers = [10, 20, 30];
const doubled = numbers.forEach((num, index) => {
  return num * 2;
});

console.log(doubled) // undefined

2. Array.map():

  • Iterates through the elements in an array.
  • "Maps" each element to a new element by calling the function on each element, creating a new array as a result.
const numbers = [10, 20, 30];
const doubled = numbers.map(num => {
  return num * 2;
});

console.log(doubled) // [20, 40, 60]

The main difference between .forEach and .map() is that .map() returns a new array. If you need the result, but do not wish to mutate the original array, .map() is the clear choice. If you simply need to iterate over an array, forEach is a fine choice.

⚝ Try this example on CodeSandbox

Q. What is unshift() method in JavaScript?

The unshift() method adds one or more elements to the beginning of an array and returns the new length of the array.

Example:

const numbers = [10, 20, 30];

console.log(numbers.unshift(40, 50)); // 5
console.log(numbers); // [40, 50, 10, 20, 30]

⚝ Try this example on CodeSandbox

Q. What is a rest parameter?

The rest parameter is used to represent an indefinite number of arguments as an array. The important point here is only the function's last parameter can be a "rest parameter".

This feature has been introduced to reduce the boilerplate code that was induced by the arguments.

Example:

function sum(...args) {
  return args.reduce((previous, current) => {
    return previous + current;
  });
}

console.log(sum(10)); // 10
console.log(sum(10, 20)); // 30
console.log(sum(10, 20, 30)); // 60

⚝ Try this example on CodeSandbox

Q. What happens if you do not use rest parameter as a last argument?

The rest parameter should be the last argument, as its job is to collect all the remaining arguments into an array.

Example: If you define a function like below it does not make any sense and will throw an SyntaxError.

function display(a, ...args, b) {
  console.log(a);
  for (let i = 0; i < args.length; i++) {
    console.log(args[i]);
  }
  console.log(b);
}

display(10, 20, 30, 40, 50);

// Output
SyntaxError: Rest element must be last element

⚝ Try this example on CodeSandbox

Q. What is difference between [] and new Array()?

[] and new Array() are two different ways of creating an array, but they are functionally equivalent.

The primary difference between them is in how they are created and in their behavior when used with certain methods.

[] is a shorthand for creating a new array. It is the preferred way to create an array in most cases, because it's more concise and easier to read. For example:

const myArray = []; // create a new empty array

On the other hand, new Array() is a constructor function that creates a new array object. It can be used to create an array of a specific length or with specific elements. For example:

const myArray = new Array(); // create a new empty array
const myOtherArray = new Array(3); // create a new array with a length of 3
const myThirdArray = new Array("a", "b", "c"); // create a new array with three elements

One potential pitfall of using new Array() is that it can be ambiguous when you pass a single argument to the constructor. For example, new Array(3) creates an array with a length of 3, but new Array("3") creates an array with a single element, the string "3". This is because the argument is treated as the value of the first element when it's a non-negative integer, but as the length of the array when it's a string or a negative integer.

In summary, [] is the preferred way to create a new array in JavaScript, while new Array() is an alternative way that can be used when you need more control over the array's length or contents.

Q. What are Array.from(), Array.of() and Array.isArray() methods?

1. Array.from():

Array.from() creates a new, shallow-copied array from an array-like or iterable object such as a Set, Map, NodeList, or string.

// From a string
console.log(Array.from('hello')); // ['h', 'e', 'l', 'l', 'o']

// From a Set
const set = new Set([1, 2, 3, 2, 1]);
console.log(Array.from(set)); // [1, 2, 3]

// With a map function
console.log(Array.from([1, 2, 3], x => x * 2)); // [2, 4, 6]

// From array-like object
console.log(Array.from({ length: 3 }, (_, i) => i + 1)); // [1, 2, 3]

2. Array.of():

Array.of() creates a new array from a variable number of arguments, regardless of number or type. It fixes the ambiguity of new Array() with a single numeric argument.

console.log(Array.of(7));       // [7]         (one element)
console.log(new Array(7));      // [ , , , , , , ] (empty array of length 7!)

console.log(Array.of(1, 2, 3)); // [1, 2, 3]

3. Array.isArray():

Array.isArray() returns true if the passed value is an array, false otherwise. It is more reliable than instanceof when working across different frames or windows.

console.log(Array.isArray([1, 2, 3]));  // true
console.log(Array.isArray('hello'));     // false
console.log(Array.isArray({ length: 3 })); // false
console.log(Array.isArray(new Array())); // true

# 8. CONTROL FLOW


Q. What are the different types of control flow statements available in JavaScript?

Control flow determines the order in which statements are executed. JavaScript provides three main categories:

CategoryStatements
Conditionalif, else if, else, switch, ternary (? :)
Looping / Iterationfor, while, do...while, for...in, for...of
Jump / Transferbreak, continue, return, throw

By default, JavaScript executes code top-to-bottom (sequential flow). Control flow statements allow branching and repetition, enabling complex logic.

Example:

// Conditional branching
const score = 78;

if (score >= 90) {
  console.log("Grade: A");
} else if (score >= 75) {
  console.log("Grade: B"); // ← executed
} else {
  console.log("Grade: C");
}

// Looping
const students = ["Alice", "Bob", "Carol"];
for (let i = 0; i < students.length; i++) {
  console.log(students[i]);
}

// Jump: exit loop early
for (let i = 0; i < 10; i++) {
  if (i === 5) break;
  console.log(i); // 0, 1, 2, 3, 4
}

Q. How does the if...else if...else ladder work, and what are its performance considerations?

The if...else if...else ladder evaluates conditions sequentially from top to bottom and executes the first block whose condition is true. Once a matching branch executes, all remaining else if and else blocks are skipped — making it short-circuit by design.

Performance consideration: JavaScript evaluates each condition until one is truthy. Placing the most frequently matched condition first reduces the number of comparisons at runtime.

Example:

function classify(temperature) {
  if (temperature < 0) {
    return "Freezing";
  } else if (temperature < 10) {
    return "Cold";
  } else if (temperature < 20) {
    return "Mild";
  } else if (temperature < 30) {
    return "Warm";
  } else {
    return "Hot";
  }
}

console.log(classify(-5));  // Freezing
console.log(classify(15));  // Mild
console.log(classify(35));  // Hot

Falsy vs. Truthy awareness:

// These values are falsy in JavaScript:
// false, 0, "", null, undefined, NaN

const value = 0;

if (value) {
  console.log("Truthy");
} else {
  console.log("Falsy"); // ← executed because 0 is falsy
}

Q. When should you use a switch statement instead of if...else if, and what are its pitfalls?

A switch statement compares a single expression against multiple discrete values using strict equality (===). It is preferable over if...else if when:

  • You are comparing one variable against many possible literal values.
  • Readability is a priority (avoids repetitive condition checks against the same variable).

Pitfall — Fall-through: Without a break statement, execution "falls through" to the next case, which is a common source of bugs.

Example:

const day = "Monday";

switch (day) {
  case "Monday":
  case "Tuesday":
  case "Wednesday":
  case "Thursday":
  case "Friday":
    console.log("Weekday");
    break;
  case "Saturday":
  case "Sunday":
    console.log("Weekend");
    break;
  default:
    console.log("Unknown day");
}
// Output: Weekday

Intentional fall-through example:

// Grouping cases that share behavior — intentional fall-through
const statusCode = 404;

switch (statusCode) {
  case 400:
  case 401:
  case 403:
  case 404:
    console.log("Client error");
    break;
  case 500:
  case 502:
  case 503:
    console.log("Server error");
    break;
  default:
    console.log("Unknown status");
}
// Output: Client error

Pitfall — switch uses ===, not ==:

const x = "1";
switch (x) {
  case 1:
    console.log("Number 1"); // NOT matched
    break;
  case "1":
    console.log("String '1'"); // ← matched
    break;
}

Q. Explain the difference between for, for...in, and for...of loops with practical examples.

LoopIterates OverUse Case
forIndex-based counterArrays, precise iteration control
for...inEnumerable property keysObject properties (avoid for arrays)
for...ofIterable values (arrays, strings, Maps, Sets, generators)Arrays, strings, and other iterables

Example:

// --- for loop: classic index-based ---
const arr = [10, 20, 30];

for (let i = 0; i < arr.length; i++) {
  console.log(`Index ${i}: ${arr[i]}`);
}
// Index 0: 10 | Index 1: 20 | Index 2: 30

// --- for...in: object property keys ---
const user = { name: "Alice", age: 30, role: "admin" };

for (const key in user) {
  console.log(`${key}: ${user[key]}`);
}
// name: Alice | age: 30 | role: admin

// --- for...of: iterable values ---
const fruits = ["apple", "banana", "cherry"];

for (const fruit of fruits) {
  console.log(fruit);
}
// apple | banana | cherry

// for...of also works on strings
for (const char of "hello") {
  process.stdout.write(char + " ");
}
// h e l l o

// for...of with Map
const scores = new Map([["Alice", 95], ["Bob", 87]]);
for (const [name, score] of scores) {
  console.log(`${name}: ${score}`);
}

Why avoid for...in on arrays?

const arr = [1, 2, 3];
arr.custom = "oops"; // accidentally adding a property

for (const key in arr) {
  console.log(key); // "0", "1", "2", "custom" ← includes inherited/custom keys!
}

for (const val of arr) {
  console.log(val); // 1, 2, 3 ← only values, safe
}

Real-World Use Case:

In a Node.js data-processing pipeline, for...of with await (inside an async function) iterates over a paginated API result set sequentially — something forEach cannot do since it ignores returned Promises. for...in is used to dynamically enumerate config object keys when serializing environment-specific settings.

Q. How do while and do...while loops differ, and when should each be used?

LoopCondition CheckMinimum ExecutionsUse When
whileBefore each iteration0 (may never run)Number of iterations is unknown; condition may be false initially
do...whileAfter each iteration1 (always runs once)Body must execute at least once regardless of condition

Example:

// --- while loop ---
let stock = 5;

while (stock > 0) {
  console.log(`Items remaining: ${stock}`);
  stock--;
}
// Runs 5 times. If stock were 0 initially, it would never run.

// --- do...while loop ---
let attempts = 0;
let pin;

do {
  pin = prompt("Enter 4-digit PIN:"); // Must show prompt at least once
  attempts++;
} while (pin !== "1234" && attempts < 3);

console.log(attempts === 3 ? "Locked out" : "Access granted");

Infinite loop guard — always ensure the condition can become false:

// DANGER: this never terminates
// while (true) { }

// SAFE: controlled exit with break
let counter = 0;
while (true) {
  counter++;
  if (counter >= 5) break;
}
console.log(counter); // 5

Q. What is the purpose of break and continue statements, and how do they interact with nested loops?

  • break: Immediately terminates the innermost loop or switch statement and transfers control to the statement following it.
  • continue: Skips the current iteration of the innermost loop and jumps to the next iteration's condition check.

Example:

// --- break: stop on first match ---
const inventory = [
  { id: 1, name: "Widget A", inStock: false },
  { id: 2, name: "Widget B", inStock: true },
  { id: 3, name: "Widget C", inStock: true },
];

let firstAvailable = null;
for (const item of inventory) {
  if (item.inStock) {
    firstAvailable = item;
    break; // stop searching once found
  }
}
console.log(firstAvailable); // { id: 2, name: "Widget B", inStock: true }

// --- continue: skip unwanted items ---
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const evens = [];

for (const n of numbers) {
  if (n % 2 !== 0) continue; // skip odd numbers
  evens.push(n);
}
console.log(evens); // [2, 4, 6, 8, 10]

Labeled statements — breaking outer loops:

// Without labels, break only exits the inner loop
outerLoop: for (let i = 0; i < 3; i++) {
  for (let j = 0; j < 3; j++) {
    if (i === 1 && j === 1) {
      break outerLoop; // exits BOTH loops
    }
    console.log(`i=${i}, j=${j}`);
  }
}
// i=0,j=0 | i=0,j=1 | i=0,j=2 | i=1,j=0  ← stops here

Real-World Use Case:

In a search autocomplete engine, break short-circuits a loop over a large dataset once the desired number of suggestions is collected, preventing unnecessary iterations. continue is used in data-cleaning pipelines to skip null or malformed records without nesting the entire logic in an extra if block — keeping cyclomatic complexity low and the code easier to review.

Q. What is the ternary operator and how does it compare to if...else for conditional expressions?

The ternary operator (condition ? expressionIfTrue : expressionIfFalse) is JavaScript's only three-operand operator. It evaluates a condition and returns one of two expressions. Unlike if...else (a statement), the ternary is an expression — it produces a value and can be used inline.

Example:

// if...else statement — cannot be used inline
let label;
const isLoggedIn = true;

if (isLoggedIn) {
  label = "Logout";
} else {
  label = "Login";
}

// Ternary expression — equivalent, concise, assignable
const label2 = isLoggedIn ? "Logout" : "Login";

console.log(label2); // "Logout"

// Inline in JSX (React)
// <button>{isLoggedIn ? "Logout" : "Login"}</button>

// Nested ternary — use sparingly, prefer if...else for readability
const score = 72;
const grade = score >= 90 ? "A"
            : score >= 75 ? "B"
            : score >= 60 ? "C"
            : "F";

console.log(grade); // "C"

When NOT to use ternary:

// Bad: using ternary for side effects reduces readability
isLoggedIn ? doLogout() : doLogin();

// Better: use if...else for side effects
if (isLoggedIn) {
  doLogout();
} else {
  doLogin();
}

Real-World Use Case:

In React, the ternary operator is the standard pattern for conditional rendering within JSX since JSX is an expression context — if statements cannot appear inside {} interpolation. For example, rendering a loading spinner vs. a data table based on isLoading state is universally handled with a ternary.

Q. What are the nullish coalescing (??) and optional chaining (?.) operators, and how do they relate to control flow?

Both operators introduced in ES2020 reduce verbose defensive if checks for null/undefined values:

  • Nullish coalescing (??): Returns the right-hand operand when the left is null or undefined (not other falsy values like 0 or "").
  • Optional chaining (?.): Short-circuits and returns undefined instead of throwing a TypeError when accessing a property on null or undefined.

Example:

// --- Nullish Coalescing ?? ---
const userAge = 0; // falsy but valid!

// Old approach (bug: treats 0 as falsy)
const age1 = userAge || 18; // 18  ← WRONG: overwrites valid 0
// Correct approach
const age2 = userAge ?? 18; // 0   ← CORRECT: 0 is not null/undefined

console.log(age1, age2); // 18, 0

// --- Optional Chaining ?. ---
const user = {
  profile: {
    address: null
  }
};

// Without optional chaining — throws TypeError
// console.log(user.profile.address.city); // TypeError!

// With optional chaining — safe
console.log(user.profile?.address?.city); // undefined (no error)

// Works on methods too
const result = user.getPermissions?.(); // undefined if method doesn\'t exist

// Works on arrays
const first = user.tags?.[0]; // undefined if tags is null/undefined

// --- Combined usage ---
const city = user.profile?.address?.city ?? "Unknown City";
console.log(city); // "Unknown City"

Real-World Use Case:

When consuming third-party REST APIs or GraphQL responses, deeply nested fields may be absent depending on the query. Optional chaining eliminates dozens of guard clauses like if (response && response.data && response.data.user). Nullish coalescing is critical when a user-configurable setting has 0 or "" as a valid value — falling back with || would incorrectly override those legitimate settings.

Q. What are labeled statements in JavaScript and when are they appropriate?

A labeled statement is an identifier followed by a colon (:) placed before a loop or block. Labels can be referenced by break and continue to control the flow of outer loops from within a nested loop. They are rarely used — but in specific nested-iteration scenarios they can be clearer than managing manual flags.

Example:

// --- Using label with break to exit a matrix search ---
function findValue(matrix, target) {
  let found = false;

  search: for (let row = 0; row < matrix.length; row++) {
    for (let col = 0; col < matrix[row].length; col++) {
      if (matrix[row][col] === target) {
        console.log(`Found at [${row}][${col}]`);
        found = true;
        break search; // exit both loops immediately
      }
    }
  }

  return found;
}

const matrix = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9],
];

findValue(matrix, 5); // Found at [1][1]

// --- Using label with continue ---
outer: for (let i = 0; i < 3; i++) {
  for (let j = 0; j < 3; j++) {
    if (j === 1) continue outer; // skip rest of inner loop, go to next outer iteration
    console.log(`i=${i}, j=${j}`);
  }
}
// i=0,j=0 | i=1,j=0 | i=2,j=0

Alternative to a flag variable:

// Without labels — requires a flag
let found = false;
for (let i = 0; i < rows && !found; i++) {
  for (let j = 0; j < cols; j++) {
    if (grid[i][j] === target) { found = true; break; }
  }
}

// With labels — no flag needed, intent is clearer
searchGrid: for (let i = 0; i < rows; i++) {
  for (let j = 0; j < cols; j++) {
    if (grid[i][j] === target) break searchGrid;
  }
}

Real-World Use Case:

In a pathfinding algorithm (e.g., BFS/DFS on a 2D game map) or a seat-selection engine scanning rows and seats in a stadium booking system, a labeled break exits all nested loops the moment a valid seat is located — avoiding a boolean flag variable that would otherwise pollute the outer scope and add an extra condition to every loop header.

Q. What is short-circuit evaluation in JavaScript and how does it control execution flow?

JavaScript's logical operators (&&, ||, ??) use short-circuit evaluation: the right-hand operand is only evaluated if necessary. This is not just a performance optimization — it is a widely used control flow mechanism.

OperatorShort-circuits when left side isReturns
&& (AND)falsyLeft operand (skips right)
|| (OR)truthyLeft operand (skips right)
?? (Nullish)not null/undefinedLeft operand (skips right)

Example:

// --- && short-circuit: right side only runs if left is truthy ---
const user = { isAdmin: true };

user.isAdmin && console.log("Admin panel loaded"); // ← runs
null && console.log("This never runs");             // ← skipped

// --- || short-circuit: right side only runs if left is falsy ---
const config = null;
const settings = config || { theme: "dark", lang: "en" }; // default fallback
console.log(settings); // { theme: "dark", lang: "en" }

// --- Practical: guard before calling a function ---
function logError(err) {
  err && err.message && console.error(err.message);
}

logError(null);            // nothing happens (safe)
logError({ message: "Failed" }); // "Failed"

// --- Chaining for conditional execution (React pattern) ---
const isLoading = false;
const data = [1, 2, 3];

// Equivalent to: if (!isLoading && data.length > 0) render()
const output = !isLoading && data.length > 0 && "Render data";
console.log(output); // "Render data"

Gotcha — using && for side effects:

// This is valid but can be confusing:
isAuthenticated && fetchDashboard();

// Prefer explicit if for clarity in complex flows:
if (isAuthenticated) {
  fetchDashboard();
}

Real-World Use Case:

In React, && short-circuit is the idiomatic pattern for conditional rendering: {isLoggedIn && <UserMenu />} renders the component only when isLoggedIn is truthy, with no JSX if block needed. The || operator is universally used for default parameter fallbacks before optional chaining became standard. In Node.js middleware chains, && guards ensure a dependency (e.g., a database connection) is available before proceeding to query logic.

# 9. FUNCTIONS


Q. What are the benefits of using arrow function over es5 function?

Arrows is a new syntax for functions, which brings several benefits:

  • Arrow syntax automatically binds this to the surrounding code's context
  • The syntax allows an implicit return when there is no body block, resulting in shorter and simpler code in some cases
  • Last but not least, => is shorter and simpler than function, although stylistic issues are often subjective

Example 01: Arrow Function with No Argument

If a function doesn't take any argument, then you should use empty parentheses.

let greet = () => console.log('Hello');
greet(); // Hello

Example 02: Arrow Function with One Argument

If a function has only one argument, you can omit the parentheses.

let greet = x => console.log(x);
greet('Hello'); // Hello 

Example 03: Arrow Function as an Expression

You can also dynamically create a function and use it as an expression.

let age = 25;

let welcome = (age < 18) ?
  () => console.log('Baby') :
  () => console.log('Adult');

welcome(); // Adult

Example 04: Multiline Arrow Functions

If a function body has multiple statements, you need to put them inside curly brackets {}.

let area = (r) => {
  const pi = 3.14;
  return pi * r * r;
}

let result = area(10);
console.log(result); // 314

Note: Unlike regular functions, arrow functions do not have their own this. The value of this inside an arrow function remains the same throughout the lifecycle of the function and is always bound to the value of this in the closest non-arrow parent function.

⚝ Try this example on CodeSandbox

Q. What is the benefit of using the arrow syntax for a method in a constructor?

The main advantage of using an arrow function as a method inside a constructor is that the value of this gets set at the time of the function creation and can't change after that. So, when the constructor is used to create a new object, this will always refer to that object.

Example:

const Person = function(firstName) {
  this.firstName = firstName;
  this.sayName1 = function() { console.log(this.firstName); }; // regular function
  this.sayName2 = () => { console.log(this.firstName); };      // arrow function
};

const john = new Person('John');
const dave = new Person('Dave');

john.sayName1(); // John
john.sayName2(); // John

// Regular function: 'this' can be changed with call/apply/bind
john.sayName1.call(dave); // Dave
john.sayName2.call(dave); // John — arrow function ignores call/apply/bind

// Detached from object: regular function loses 'this'
const fn1 = john.sayName1;
fn1(); // undefined

const fn2 = john.sayName2;
fn2(); // John — arrow function retains original 'this'

Q. Difference between Function, Method and Constructor calls in JavaScript?

1. Functions: The simplest usages of function call:

function display(name) {
  return "Hello " + name;
}

display("World"); // "Hello World"

2. Methods: in JavaScript are nothing more than object properties that are functions.

var obj = {
  display : function() {
    return "Hello " + this.name;
  },
  name: 'Minali Peri'
}
obj.display();  // "Hello Minali Peri"

3. Constructors: Like function and method, constructors are defined with function.

function Employee(name, age) {
  this.name = name;
  this.age = age;
}

var emp1 = new Employee('Drishya Sama', 28);
emp1.name; // "Drishya Sama"
emp1.age; // 28

Unlike function calls and method calls, a constructor call new Employee('Drishya Sama', 28) creates a new object and passes it as the value of this, and implicitly returns the new object as its result. The primary role of the constructor function is to initialize the object.

Q. When you should not use arrow functions in ES6?

An arrow function is a shorter syntax for a function expression and does not have its own this, arguments, super, or new.target. These function are best suited for non-method functions, and they cannot be used as constructors.

Arrow functions in ES6 has two limitations:

  • Do not work with new
  • Fixed this bound to scope at initialisation

When should not use Arrow Functions:

1. Object methods:

The counter object has two methods: current() and next(). The current() method returns the current counter value and the next() method returns the next counter value.

// Usin arrow function
const counter = {
  count: 0,
  next: () => ++this.count,
  current: () => this.count
};

console.log(counter.next()); // NaN

2. Event handlers:

If we click the button, we would get a TypeError. It is because this is not bound to the button, but instead bound to its parent scope.

let button = document.getElementById('press');

button.addEventListener('click', () => {
  this.classList.toggle('on');
});

3. Prototype methods:

The this value in these next() and current() methods reference the global object. Since the this value inside the methods to reference the Counter object, it needs to use the regular functions instead

function Counter() {
    this.count = 0;
}

Counter.prototype.next = () => {
    return this.count;
};

Counter.prototype.current = () => {
    return ++this.next;
}

4. Functions that use the arguments object:

Arrow functions don't have the arguments object. Therefore, if a function that uses arguments object, you cannot use the arrow function.

const concat = (separator) => {
    let args = Array.prototype.slice.call(arguments, 1);
    return args.join(separator);
}

⚝ Try this example on CodeSandbox

Q. What are the properties of function objects in javascript?

JavaScript function objects are used to define a piece of JavaScript code. This code can be called within a JavaScript code as and when required.

Javascript Function Objects Property:

NameDescription
argumentsAn array corresponding to the arguments passed to a function.
arguments.calleeRefers the currently executing function.
arguments.lengthRefers the number of arguments defined for a function.
constructorSpecifies the function that creates an object.
lengthThe number of arguments defined by the function.
prototypeAllows adding properties to a Function object.

Q. What is a first class function?

In javaScript, functions can be stored as a variable inside an object or an array as well as it can be passed as an argument or be returned by another function. That makes function first-class function in JavaScript.

A first-class function means functions are treated as values — they can be:

  • Assigned to variables: const greet = function() { return "Hello"; }
  • Passed as arguments: arr.map(fn)
  • Returned from other functions: function outer() { return function inner() {} }
  • Stored in data structures: const obj = { fn: () => {} }

JavaScript fully supports first-class functions, which enables patterns like callbacks, higher-order functions, and closures.

Example 01: Assign a function to a variable

const message = function() {
   console.log("Hello World!");
}

message(); // Invoke it using the variable

Example 02: Pass a function as an Argument

function sayHello() {
   return "Hello, ";
}
function greeting(helloMessage, name) {
  console.log(helloMessage() + name);
}
// Pass `sayHello` as an argument to `greeting` function
greeting(sayHello, "JavaScript!");

Example 03: Return a function

function sayHello() {
   return function() {
      console.log("Hello!");
   }
}

Example 04: Using a variable

const sayHello = function() {
   return function() {
      console.log("Hello!");
   }
}
const myFunc = sayHello();
myFunc();

Example 05: Using double parentheses

function sayHello() {
   return function() {
      console.log("Hello!");
   }
}
sayHello()();

We are using double parentheses ()() to invoke the returned function as well.

⚝ Try this example on CodeSandbox

Q. What is a higher order function?

A Higher-Order function is a function that receives a function as an argument or returns the function as output.

For example, Array.prototype.map(), Array.prototype.filter() , Array.prototype.forEach() and Array.prototype.reduce() are some of the Higher-Order functions in javascript.

Example 01: .map()

const array = [10, 20, 30];

const result = array.map(function (item) {
  return item * 2;
});
console.log(result); // [20, 40, 60]

Example 02: .filter()

const randomNumbers = [4, 11, 42, 14, 39];

const filteredArray = randomNumbers.filter((number) => {
  return number > 15;
});
console.log(filteredArray); // [42, 39]

Example 03: .forEach()

const numbers = [28, 77, 45];

numbers.forEach((number) => {
  console.log(number);
});

Example 04: .reduce()

const arrayOfNumbers = [10, 20, 30];

const sum = arrayOfNumbers.reduce((accumulator, currentValue) => {
  return accumulator + currentValue;
});
console.log("Sum: " + sum); // 60

⚝ Try this example on CodeSandbox

Q. What is a unary function?

Unary function (i.e. monadic) is a function that accepts exactly one argument. It stands for single argument accepted by a function.

// Unary function
const unaryFunction = (number) => number + 10;

console.log(unaryFunction(10)); // 20

⚝ Try this example on CodeSandbox

Q. What is currying function?

Currying is the process of taking a function with multiple arguments and turning it into a sequence of functions each with only a single argument.

In other words, when a function, instead of taking all arguments at one time, takes the first one and return a new function that takes the second one and returns a new function which takes the third one, and so forth, until all arguments have been fulfilled.

// Normal function
const add = (a, b, c) => {
  return a + b + c;
};
console.log(add(10, 10, 10)); // 30

// Currying function
const addCurry = (a) => {
  return (b) => {
    return (c) => {
      return a + b + c;
    };
  };
};
console.log(addCurry(20)(20)(20)); // 60

Note: Curried functions are great to improve code re-usability and functional composition.

⚝ Try this example on CodeSandbox

Q. What is a pure function?

Pure functions are functions that accept an input and returns a value without modifying any data outside its scope(Side Effects). Its output or return value must depend on the input/arguments and pure functions must return a value.

Example: Pure Function

It is a pure function because you always get a Hello <name> as output for the <name> pass as an input.

// Pure Function

function sayGreeting(name) {
  return `Hello ${name}`;
}

console.log(sayGreeting("World"));

Example: Not Pure Function

The function's output now depends on an outer state called greeting. What if someone changes the value of the greeting variable to Hola? It will change the output of the sayGreeting() function even when you pass the same input.

let greeting = "Hello";

function sayGreeting(name) {
  return `${greeting} ${name}`;
}

A function must pass two tests to be considered pure:

  • Same inputs always return same outputs
  • No side-effects

Benefits:

  • Predictable: It produces a predictable output for the same inputs.
  • Readable: Anyone reading the function as a standalone unit can understand its purpose completely.
  • Reusable: Can reuse the function at multiple places of the source code without altering its and the caller's behavior.
  • Testable: We can test it as an independent unit.

Q. What is memoization in JavaScript?

Memoization is a programming technique which attempts to increase a function's performance by caching its previously computed results.

Each time a memoized function is called, its parameters are used to index the cache. If the data is present, then it can be returned, without executing the entire function. Otherwise the function is executed and then the result is added to the cache.

// Memoized function to Add Number

const memoizedAdd = () => {
  let cache = {};
  return (number) => {
    if (number in cache) {
      console.log('Fetching from cache: ');
      return cache[number];
    }
    else {
      console.log('Calculating result: ');
      let result = number + 10;
      cache[number] = result;
      return result;
    }
  }
}
// returned function from memoizedAdd
const sum = memoizedAdd();

console.log(sum(10)); // Calculating result: 20
console.log(sum(10)); // Fetching from cache: 20

⚝ Try this example on CodeSandbox

Q. What is an arguments object?

The arguments object is an Array-like object ( arguments ) accessible inside functions that contains the values of the arguments passed to that function.

Example:

/**
 * Arguments Object
 */

function sum() {
  let total = 0;
  for (let i = 0, len = arguments.length; i < len; ++i) {
    total += arguments[i];
  }
  return total;
}

sum(10, 20, 30); // returns 60

⚝ Try this example on CodeSandbox

Q. What is the way to find the number of parameters expected by a function?

The length property indicates the number of parameters expected by the function.

// function.length

function fun1() {}
console.log(fun1.length); // 0

function fun2(arg1, arg2) {}
console.log(fun2.length); // 2

⚝ Try this example on CodeSandbox

Q. What is bind method in javascript?

The bind() method creates a new function, when invoked, has the this sets to a provided value. The bind() method allows an object to borrow a method from another object without making a copy of that method. This is known as function borrowing in JavaScript.

Example:

/**
 * bind() function
 */
const person = {
  firstName: "Chhavi",
  lastName: "Goswami",
  fullName: function () {
    return this.firstName + " " + this.lastName;
  }
};

const member = {
  firstName: "Vasuda",
  lastName: "Sahota"
};

let fullName = person.fullName.bind(member);
console.log(fullName()); // Vasuda Sahota

⚝ Try this example on CodeSandbox

Q. What is an anonymous function?

An anonymous function is a function without a name. Anonymous functions are commonly assigned to a variable name or used as a callback function.

Example 01: Anonymous function

let show = function () {
  console.log("Anonymous function");
};
show();

Example 02: anonymous functions as arguments

setTimeout(function () {
  console.log("Execute later after 1 second");
}, 1000);

Example 03: Immediately invoked function execution

const person = {
  firstName: "Ayaan",
  lastName: "Memon"
};

(function () {
  console.log(person.firstName + " " + person.lastName); // Ayaan Memon
})(person);

Example 04: Arrow functions

let add = (a, b) => a + b;

add(10, 20); // 30

⚝ Try this example on CodeSandbox

Q. Explain the difference between function foo() {} and var foo = function() {}?

1. Function Declaration:

Function declarations are evaluated upon entry into the enclosing scope, before any step-by-step code is executed. The function's name (foo) is added to the enclosing scope.

foo(); // Function Declaration Example!

function foo() {
  console.log("Function Declaration Example!");
}

2. Function Expression:

Function expressions are evaluated as part of the step-by-step code, at the point where they appear. That one creates a function with no name, which it assigns to the foo variable.

foo(); // TypeError: foo is not a function

var foo = function() {
  console.log(typeof foo); // function
};
console.log(typeof foo);     // undefined

⚝ Try this example on CodeSandbox

Q. When to use function declarations and expressions in JavaScript?

Function Declarations:

A declared function is "saved for later use", and will be executed later, when it is invoked (called).

// Function declaration
function add(num1, num2) {
	return num1 + num2;
}

function is only declared here. For using it, it must be invoked using function name. e.g add(10, 20);

Function Expression:

A function expression can be stored in a variable:

// Function expression
var add = function (num1, num2) {
	return num1 + num2;
};

After a function expression has been stored in a variable, the variable can be used as a function. Functions stored in variables do not need function names. They are always invoked (called) using the variable name.

Difference:

  • Function declarations load before any code is executed while Function expressions load only when the interpreter reaches that line of code.
  • Similar to the var statement, function declarations are hoisted to the top of other code. Function expressions aren't hoisted, which allows them to retain a copy of the local variables from the scope where they were defined.

Benefits of Function Expressions:

There are several different ways that function expressions become more useful than function declarations.

  • As closures
  • As arguments to other functions
  • As Immediately Invoked Function Expressions (IIFE)

Q. What is the difference between a method and a function in javascript?

1. Function:

A function is a piece of code that is called by name and function itself not associated with any object and not defined inside any object. It can be passed data to operate on (i.e. parameter) and can optionally return value.

Example:

// Function 
function message(msg) {
  return msg;
}

// Call the function
message("Welcome to JavaScript");

Here, message() function call is not associated with object hence not invoked through any object.

2. Method:

A JavaScript method is a property of an object that contains a function definition. Methods are functions stored as object properties.

Example:

// Method
let employee = {
  firstName: "Ajay",
  lastName: "Nagi",
  getName: function () {
    return "Employe Name: " + this.firstName + " " + this.lastName;
  }
};

// Call the method
console.log(employee.getName());

Here employee is an object and getName is a method which is associated with employee.

⚝ Try this example on CodeSandbox

Q. What is Function binding?

Function binding ( .bind() ) is a method on the prototype of all functions in JavaScript. It allows to create a new function from an existing function, change the new function's this context, and provide any arguments you want the new function to be called with. The arguments provided to bind will precede any arguments that are passed to the new function when it is called.

Example:

// Function Binding
const person = {
  firstName: "Nirupama",
  lastName: "Randhawa",
  getName: function () {
    return this.firstName + " " + this.lastName;
  }
};

const member = {
  firstName: "Alisha",
  lastName: "Chhabra"
};

let getName = person.getName.bind(member);
console.log(getName()); // Alisha Chhabra

⚝ Try this example on CodeSandbox

Q. Explain how this works in JavaScript?

The this keyword refers to an object. Which object depends on how this is being invoked (used or called). The this keyword refers to different objects depending on how it is used.

  • In an object method, this refers to the object.
  • Alone, this refers to the global object.
  • In a function, this refers to the global object.
  • In a function, in strict mode, this is undefined.
  • In an event, this refers to the element that received the event.
  • Methods like call(), apply(), and bind() can refer this to any object.

Example:

// this keyword in object method

const person = {
  firstName: "Nirupama",
  lastName: "Randhawa",
  getName: function () {
    return this.firstName + " " + this.lastName;
  }
};

Q. What is generator in JS?

Generator-Function:

A generator-function is defined like a normal function, but whenever it needs to generate a value, it does so with the yield keyword rather than return. The yield statement suspends function's execution and sends a value back to caller, but retains enough state to enable function to resume where it is left off. When resumed, the function continues execution immediately after the last yield run.

Syntax:

function* gen() {
     yield 1;
     yield 2;
     yield 3;
     ...
}

Generator-Object:

Generator functions return a generator object. Generator objects are used either by calling the next method on the generator object or using the generator object in a "for in" loop.

Example:

// Generate Function

function* fun() {
  yield 10;
  yield 20;
  yield 30;
}

// Calling the Generate Function
var gen = fun(); // returns a Generator object (doesn\'t run yet)
gen.next(); // { value: 10, done: false }
gen.next(); // { value: 20, done: false }
gen.next(); // { value: 30, done: false }
gen.next(); // { value: undefined, done: true }

⚝ Try this example on CodeSandbox

Q. Compare Async-Await and Generators usage to achive same functionality?

1. Generators/Yield:

Generators are objects created by generator functions — functions with an * (asterisk) next to their name. The yield keyword pauses generator function execution and the value of the expression following the yield keyword is returned to the generator's caller. It can be thought of as a generator-based version of the return keyword.

Example:

// Generator function

const generator = (function* () {
  // waiting for .next()
  const number = yield 5;
  // waiting for .next()
  console.log(number); // => 15
})();

console.log(generator.next()); // => { done: false, value: 5 }
console.log(generator.next(15)); // => { done: true, value: undefined }

2. Async/Await:

Async keyword is used to define an asynchronous function, which returns a AsyncFunction object.

Await keyword is used to pause async function execution until a Promise is fulfilled, that is resolved or rejected, and to resume execution of the async function after fulfillments. When resumed, the value of the await expression is that of the fulfilled Promise.

Key points:

  1. Await can only be used inside an async function.
  2. Functions with the async keyword will always return a promise.
  3. Multiple awaits will always run in sequential order under a same function.
  4. If a promise resolves normally, then await promisereturns the result. But in case of a rejection it throws the error, just if there were a throw statement at that line.
  5. Async function cannot wait for multiple promises at the same time.
  6. Performance issues can occur if using await after await as many times one statement doesn't depend on the previous one.

Example:

// Async/Await

async function asyncFunction() {
  const promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("resolved!"), 1000);
  });

  const result = await promise;
  // wait till the promise resolves (*)

  console.log(result); // "resolved!"
}

asyncFunction();

Generator and Async-await — Comparison:

  1. Generator functions/yield and Async functions/await can both be used to write asynchronous code that "waits", which means code that looks as if it was synchronous, even though it really is asynchronous.
  2. Generator function are executed yield by yield i.e one yield-expression at a time by its iterator (the next method) where as Async-await, they are executed sequential await by await.
  3. Async/await makes it easier to implement a particular use case of Generators.
  4. The return value of Generator is always {value: X, done: Boolean} where as for Async function it will always be a promise that will either resolve to the value X or throw an error.
  5. Async function can be decomposed into Generator and promise implementation which are good to know stuff.

⚝ Try this example on CodeSandbox

Q. How do you compare two date objects?

Two dates can be compared by converting them into numeric values using date.getTime() method to correspond to their time. Also, the relational operators <, <=, >, >= can be used to compare JavaScript dates.

However, the equality operators ==, !=, ===, !== cannot be used to compare (the value of) dates because:

  • Two distinct objects are never equal for either strict or abstract comparisons.
  • An expression comparing Objects is only true if the operands reference the same Object.

Example:

const date1 = new Date('2026-05-16T12:00:00');
const date2 = new Date('2026-05-16T12:00:00');

// Checking for equality
if (date1.getTime() === date2.getTime()) {
    console.log("Both dates are exactly equal");
}

// Checking before or after
if (date1.getTime() < date2.getTime()) {
    console.log("date1 is before date2");
}

⚝ Try this example on CodeSandbox

Q. What are closures?

A closure is the combination of a function and the lexical environment within which that function was declared. i.e, it is an inner function that has access to the outer or enclosing function's variables.

Closure is useful in hiding implementation detail in JavaScript. In other words, it can be useful to create private variables or functions.

Lexical Scope:

In lexical scoping, free variables must belong to a parent scope. JavaScript uses lexical scoping — a function's scope is determined by where it is declared, not where it is called.

Example 01:

/**
 * Lexical Scope
 **/

function init() {
  let name = "JavaScript closures"; // name is a local variable created by init
  function displayName() {
    // displayName() is the inner function, a closure
    console.log(name); // use variable declared in the parent function
  }
  return displayName;
}

var closure = init();
closure(); // Output: "JavaScript closures"

As per the above code, the inner function displayName() has access to the variable name from the outer function init(), even after init() has returned. This is a closure.

⚝ Try this example on CodeSandbox

Example 02: Private Counter

A common use of closures is to create private state that cannot be accessed or modified directly from outside.

/**
 * Closure - Private Counter
 **/

function makeCounter() {
  let count = 0; // private variable

  return {
    increment() { count++; },
    decrement() { count--; },
    getCount()  { return count; }
  };
}

const counter = makeCounter();
counter.increment();
counter.increment();
counter.increment();
counter.decrement();
console.log(counter.getCount()); // Output: 2

// count is not directly accessible
console.log(typeof count); // Output: "undefined"

The variable count is encapsulated inside makeCounter and can only be modified through the returned methods.

Q. What is callback() function in javascript?

A callback function is a function passed into another function as an argument, which is then invoked inside the outer function to complete some kind of routine or action.

Example:

/**
 * Callback function
 */
function greet(name, callback) {
  console.log('Hello, ' + name);
  callback();
}

function sayBye() {
  console.log('Goodbye!');
}

greet('Alice', sayBye);
// Hello, Alice
// Goodbye!

Key points:

  • Callbacks can be named or anonymous functions
  • They enable asynchronous and non-blocking code
  • Overusing nested callbacks leads to "callback hell" — solved by Promises or async/await

Note: callbacks are often used to continue code execution after an asynchronous operation has completed — these are called asynchronous callbacks..

⚝ Try this example on CodeSandbox

Q. How to avoid callback hell in javascript?

Callback hell occurs when asynchronous operations are deeply nested inside one another, creating an unreadable "Pyramid of Doom"

Example:

// Callback hell
getUser(id, function(user) {
  getOrders(user, function(orders) {
    getDetails(orders[0], function(details) {
      process(details, function(result) {
        console.log(result); // deeply nested
      });
    });
  });
});

Techniques for avoiding callback hell:

1. Promises — chain .then() instead of nesting:

getUser(id)
  .then(user => getOrders(user))
  .then(orders => getDetails(orders[0]))
  .then(details => process(details))
  .then(result => console.log(result))
  .catch(err => console.error(err));

2. async/await — most readable, looks synchronous:

async function run() {
  try {
    const user    = await getUser(id);
    const orders  = await getOrders(user);
    const details = await getDetails(orders[0]);
    const result  = await process(details);
    console.log(result);
  } catch (err) {
    console.error(err);
  }
}

3. Named functions — extract callbacks into separate named functions:

function onDetails(details) { process(details).then(console.log); }
function onOrders(orders)   { getDetails(orders[0]).then(onDetails); }
function onUser(user)       { getOrders(user).then(onOrders); }

getUser(id).then(onUser);

4. Promise.all — run independent async operations in parallel:

const [users, orders] = await Promise.all([getUsers(), getOrders()]);

Note: Recommended approach: Use async/await for sequential logic and Promise.all for parallel operations.

Q. How do you encode an URL?

JavaScript provides two built-in functions for URL encoding:

1. encodeURIComponent() — encodes a single URI component (query param value, path segment):

const param = 'hello world & name=John';
encodeURIComponent(param); // 'hello%20world%20%26%20name%3DJohn'

2. encodeURI() — encodes a full URL, preserving special URL characters (:, /, ?, #, &, =):

const url = 'https://example.com/search?q=hello world&lang=en';
encodeURI(url); // 'https://example.com/search?q=hello%20world&lang=en'

Key difference:

FunctionEncodesPreserves
encodeURIComponentEverything except A-Z a-z 0-9 - _ . ! ~ * ' ( )Nothing special
encodeURISpaces and unsafe chars: / ? # & = @ ! $ ' ( ) * + , ; ~

When to use which:

const base  = 'https://example.com/search';
const query = 'hello world & more';

// Correct: encode only the param value
const url = `${base}?q=${encodeURIComponent(query)}`;
// https://example.com/search?q=hello%20world%20%26%20more

// Wrong: encodeURI won't encode & in query values
encodeURI(`${base}?q=${query}`);
// https://example.com/search?q=hello%20world%20&%20more  ← & breaks the query

Rule of thumb: Use encodeURIComponent for query parameter values; use encodeURI for a complete URL.

Q. How do you decode an URL?

The decodeURI() function is used to decode a Uniform Resource Identifier (URI) previously created by encodeURI().

1. decodeURIComponent() — decodes a single encoded URI component:

decodeURIComponent('hello%20world%20%26%20name%3DJohn');
// 'hello world & name=John'

2. decodeURI() — decodes a fully encoded URL, leaving valid URL structure characters intact:

decodeURI('https://example.com/search?q=hello%20world&lang=en');
// 'https://example.com/search?q=hello world&lang=en'

Example — reading query params:

const url = 'https://example.com/search?q=hello%20world%20%26%20more';
const raw = url.split('?q=')[1];

decodeURIComponent(raw); // 'hello world & more'

Modern alternative — use URLSearchParams which handles encoding/decoding automatically:

const params = new URLSearchParams('q=hello%20world&lang=en');
params.get('q'); // 'hello world'

Q. How function overloading works in JavaScript?

JavaScript does not support traditional function overloading (unlike Java or C#) where multiple functions can have the exact same name but different parameters. If you define two functions with the same name, the second definition will completely overwrite the first one.

Example:

function greet(name) { return `Hello, ${name}`; }
function greet(name, title) { return `Hello, ${title} ${name}`; }

greet('Alex'); // 'Hello, undefined Alex' — second definition always wins

Ways to simulate overloading:

1. Overloading by Argument Count (arguments.length):

function calculateArea(...args) {
  if (args.length === 1) {
    return Math.PI * args[0] * args[0]; // Circle area: 1 argument (radius)
  }
  if (args.length === 2) {
    return args[0] * args[1];           // Rectangle area: 2 arguments (width, height)
  }
}

console.log(calculateArea(5));     // 78.54 (Circle)
console.log(calculateArea(4, 5));  // 20 (Rectangle)

2. Overloading by Data Type (typeof / instanceof)

function add(a, b) {
  if (typeof a === 'string') return a + b;       // string concat
  if (typeof a === 'number') return a + b;       // numeric add
}

add(1, 2);       // 3
add('foo', 'bar'); // 'foobar'

3. Overloading using an Options Object

function createUser({ name, age = 18, role = 'user' } = {}) {
  return { name, age, role };
}

createUser({ name: 'Alice', age: 20 });
createUser({ name: 'Bob', age: 30, role: 'Admin' });

4. Rest parameters for variable argument count:

function sum(...nums) {
  return nums.reduce((acc, n) => acc + n, 0);
}

sum(1, 2);       // 3
sum(1, 2, 3, 4); // 10

Recommended approach: Use the options object pattern for complex cases — it's readable, flexible, and self-documenting.

Q. What is an IIFE (Immediately Invoked Function Expression)?

An IIFE (Immediately Invoked Function Expression) is a JavaScript function that is defined and executed immediately after its creation. It is a design pattern used to execute code immediately and isolate variables from the global scope.

Syntax:

(function () {
  console.log('Runs immediately');
})();

// Arrow function version
(() => {
  console.log('Also runs immediately');
})();

Example 01:

const result = (function () {
  const message = 'Hello from IIFE';
  return message;
})();

console.log(result); // 'Hello from IIFE'
console.log(typeof message); // 'undefined' — private to the IIFE

Example 02: Create a private scope

// Counter using IIFE
const counter = (function () {
  let count = 0; // private
  return {
    increment() { return count++; },
    value()     { return count; }
  };
})();

console.log(counter.increment()); // 0
console.log(counter.value());     // 1
//console.log(count);               // ReferenceError: count is not defined

Use cases:

  • Data Privacy: Variables declared inside an IIFE cannot be accessed from the outside world. This protects your code from polluting the global scope.

  • Avoiding Naming Collisions: It prevents variables from accidentally overwriting other variables in third-party libraries or scripts.

  • State Preservation: Historically, it was used inside loops to lock in the current value of a variable during asynchronous operations.

Note:

With ES6 let/const and modules, IIFEs are less common today — block scoping and module scope solve the same problems. However, IIFEs are still useful for one-time initialization logic and encapsulating setup code.

Q. What is recursion in JavaScript?

Recursion is a technique in which a function calls itself until it reaches a base case (a condition that stops further calls). Every recursive function must have a base case to avoid infinite loops and stack overflow errors.

Example 01: Factorial

function factorial(n) {
  if (n <= 1) return 1;        // base case
  return n * factorial(n - 1); // recursive call
}

console.log(factorial(5)); // 120 (5 × 4 × 3 × 2 × 1)
console.log(factorial(0)); // 1

Example 02: Fibonacci

function fibonacci(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

console.log(fibonacci(6)); // 8 (0, 1, 1, 2, 3, 5, 8)

Example 03: Flatten a nested array

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

console.log(flattenArray([1, [2, [3, [4]], 5]])); // [1, 2, 3, 4, 5]

Recursion vs Iteration:

  • Recursion is more readable for tree/graph traversal, nested structures, and divide-and-conquer problems
  • Iteration is more performant for simple loops — use it when recursion depth could be large

Note: For deeply nested structures, iterative solutions with an explicit stack are preferred over recursion to avoid stack-overflow errors.

Q. What are nested functions in JavaScript?

A nested function is a function defined inside another function. The inner function has access to the outer function's variables and parameters through closure, but the outer function cannot access the inner function's variables.

Example:

function outerFunction(outerValue) {
  const outerVar = 'I am outer';

  function innerFunction(innerValue) {
    // inner can access outer\'s scope
    console.log(outerVar);       // 'I am outer'
    console.log(outerValue);     // passed argument
    console.log(innerValue);     // inner argument
  }

  innerFunction('I am inner');
}

outerFunction('hello');

Use case — private helper functions:

function processOrder(order) {
  function validate(o) {
    return o.quantity > 0 && o.price > 0;
  }

  function calculateTotal(o) {
    return o.quantity * o.price;
  }

  if (!validate(order)) throw new Error('Invalid order');
  return calculateTotal(order);
}

console.log(processOrder({ quantity: 3, price: 15 })); // 45

The inner helpers validate and calculateTotal are private to processOrder and not accessible from outside.

Q. What are side effects in JavaScript functions?

A side effect is any observable change that a function makes to the state outside its own scope — such as modifying a global variable, mutating an argument, writing to the DOM, or making a network request.

Functions with side effects:

let count = 0;

function increment() {
  count++; // modifies external state — side effect
}

increment();
console.log(count); // 1

Pure function (no side effects):

function add(a, b) {
  return a + b; // no external mutation, same input always gives same output
}

Common sources of side effects:

SourceExample
Mutating argumentsarr.push(item) inside a function
Global state changeModifying window or module-level variables
I/O operationsconsole.log, network calls, file writes
DOM manipulationdocument.getElementById('x').textContent = '...'

Why it matters: Functions with side effects are harder to test, reason about, and reuse. Functional programming favors isolating side effects to the edges of a system while keeping the core logic pure.

Q. What is point-free style in JavaScript?

Point-free style (also called tacit programming) is a way of defining functions without explicitly mentioning the arguments they operate on. Instead, functions are composed of other functions using combinators like map, filter, reduce, or a compose/pipe utility.

Without point-free:

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

// explicitly names the argument n
const doubled = numbers.map(n => n * 2);

With point-free:

const double = x => x * 2;

// double is passed directly — no explicit argument mentioned
const doubled = numbers.map(double);

More complex example:

const trim = str => str.trim();
const toUpperCase = str => str.toUpperCase();
const words = str => str.split(' ');

// Point-free pipeline using a pipe utility
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);

const process = pipe(trim, toUpperCase, words);

console.log(process('  hello world  ')); // ['HELLO', 'WORLD']

Point-free style leads to cleaner, more declarative code but can reduce readability when overused.

Q. What is function composition in JavaScript?

Function composition is the process of combining two or more functions where the output of one function becomes the input of the next. It is a core concept in functional programming.

compose applies functions right-to-left; pipe applies them left-to-right.

compose:

const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);

const add10  = x => x + 10;
const double = x => x * 2;
const square = x => x * x;

// square( double( add10(5) ) ) => square(double(15)) => square(30) => 900
const transform = compose(square, double, add10);
console.log(transform(5)); // 900

pipe (left-to-right):

const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);

// add10(5) => double(15) => square(30) => 900
const transform = pipe(add10, double, square);
console.log(transform(5)); // 900

Real-world example:

const trim       = str => str.trim();
const toLower    = str => str.toLowerCase();
const removeSpaces = str => str.replace(/\s+/g, '-');

const slugify = pipe(trim, toLower, removeSpaces);

console.log(slugify('  Hello World  ')); // 'hello-world'

L2: Intermediate (Junior-Mid / Developer)


# 10. Scope & Closures


Q. What is lexical scope in JavaScript, and how does it differ from dynamic scope?

Lexical scope (also called static scope) means that the scope of a variable is determined at author time — by where the function is written in the source code, not where it is called from. JavaScript uses lexical scoping exclusively. When the JavaScript engine (V8) parses the source, it builds a scope chain based on the physical nesting of functions and blocks; this chain never changes at runtime.

Dynamic scope, by contrast, resolves variable references based on the call stack at runtime. Languages like Bash and early Lisp dialects use dynamic scope. JavaScript has no dynamic scope (though this is sometimes confused for it because it is determined by the call site — this binding is separate from scope).

Key rules of lexical scope:

  • Inner functions can access variables declared in outer functions (scope chain lookup).
  • Outer functions cannot access variables declared in inner functions.
  • The lookup travels outward until the global scope is reached; if not found, ReferenceError is thrown (in strict mode) or an implicit global is created (sloppy mode).
// Lexical scope example
const greeting = 'Hello';          // global scope

function outer() {
  const name = 'Alice';            // outer function scope

  function inner() {
    const punctuation = '!';       // inner function scope
    // inner can see: punctuation, name, greeting (chain lookup)
    console.log(`${greeting}, ${name}${punctuation}`);
  }

  inner(); // 'Hello, Alice!'
  // console.log(punctuation); // ReferenceError — outer cannot see inner\'s variables
}

outer();

Real-World Use Case:

React's hook system relies on lexical scope. When you write a custom hook like useCounter, the state variables declared inside it are lexically scoped to that hook invocation — they cannot bleed into unrelated components, making stateful logic safely composable and predictable in a large codebase.

Q. Explain the difference between var, let, and const with respect to scope.

Featurevarletconst
ScopeFunctionBlockBlock
HoistedYes (as undefined)Yes (TDZ — not initialised)Yes (TDZ — not initialised)
Re-declarableYesNoNo
Re-assignableYesYesNo (binding; object contents can mutate)
Global propertyCreates window.xNoNo

Block scope means the variable only exists within the nearest enclosing {} — an if, for, while, or plain block. var ignores block boundaries and is scoped to the enclosing function (or the global object if outside any function).

function demonstrateScope() {
  if (true) {
    var  funcScoped  = 'var';   // hoisted to function top
    let  blockScoped = 'let';   // lives only inside this if-block
    const immutable  = 'const'; // same block scope as let
  }

  console.log(funcScoped);   // 'var'    
  // console.log(blockScoped); // ReferenceError 
  // console.log(immutable);   // ReferenceError 
}

// Classic var-in-loop bug
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log('var:', i), 0); // prints 3, 3, 3
}

// Fixed with let (each iteration gets its own binding)
for (let j = 0; j < 3; j++) {
  setTimeout(() => console.log('let:', j), 0); // prints 0, 1, 2
}

Real-World Use Case:

In large React/Node.js codebases, var inside for loops iterating over async operations (e.g., firing multiple API calls) was a notorious source of race-condition bugs because all callbacks shared the same i. Migrating to let (or closure-capturing patterns) eliminated entire categories of production bugs. ESLint's no-var rule enforces this at scale.

Q. What is the Temporal Dead Zone (TDZ)?

In ES6, let bindings are not subject to "variable hoisting", which means that let declarations do not move to the top of the current execution context.

Referencing the variable in the block before the initialization results in a ReferenceError (contrary to a variable declared with var, which will just have the undefined value). The variable is in a "temporal dead zone" from the start of the block until the initialization is processed.

console.log(x); //  undefined  — var is hoisted and initialised to undefined
var x = 5;

console.log(y); //  ReferenceError: Cannot access 'y' before initialization
let y = 10;

// TDZ also applies inside blocks
{
  // TDZ for 'z' starts here ↓
  // console.log(z); // ReferenceError
  let z = 42;       // TDZ ends here, z is now initialised
  console.log(z);   // 42
}

Real-World Use Case:

TDZ prevents a subtle bug in module initialisation cycles. If module A imports from module B and module B imports from module A, const exports are protected by TDZ, causing a clear ReferenceError at the problematic access point rather than silently producing undefined computations that corrupt data — making circular-dependency bugs far easier to diagnose in large monorepo setups.

Q. What is a closure and how does JavaScript implement it internally?

A closure is a function that retains a reference to its lexical environment (the scope in which it was defined) even after that outer scope has finished executing. In V8, when a function is created, the engine attaches a hidden [[Environment]] slot pointing to the current scope's environment record. As long as the inner function is reachable, the garbage collector keeps the outer environment record alive.

Practically: closures "close over" variables — not values. They hold a live reference to the variable binding, so mutations to that variable are visible inside the closure.

function makeCounter(initialValue = 0) {
  let count = initialValue; // this variable is closed over

  return {
    increment() { return ++count; },
    decrement() { return --count; },
    reset()     { count = initialValue; return count; },
    value()     { return count; },
  };
}

const counter = makeCounter(10);
console.log(counter.increment()); // 11
console.log(counter.increment()); // 12
console.log(counter.decrement()); // 11
console.log(counter.value());     // 11

// count is NOT accessible from outside
// console.log(count); // ReferenceError

Real-World Use Case:

The Redux store implementation (createStore) uses closures to encapsulate currentState, currentReducer, and listeners. External code can only interact with state through the returned dispatch, getState, and subscribe functions — a closure-based module pattern that enforces immutable, predictable state management at scale.

Q. How do closures enable the Module Pattern and data encapsulation?

The Module Pattern (and its IIFE variant) uses closures to create private state — something JavaScript classes did not natively support before ES2022 private fields (#). An IIFE runs once, defines private variables, and returns a public API. Only the returned methods can access the private variables via closure.

Example:

// Classic Module Pattern (IIFE-based)
const BankAccount = (() => {
  // Private state — not accessible from outside
  let _balance   = 0;
  const _history = [];

  function _log(action, amount) {
    _history.push({ action, amount, balance: _balance, ts: Date.now() });
  }

  // Public API
  return {
    deposit(amount) {
      if (amount <= 0) throw new RangeError('Amount must be positive');
      _balance += amount;
      _log('deposit', amount);
      return this;
    },
    withdraw(amount) {
      if (amount > _balance) throw new Error('Insufficient funds');
      _balance -= amount;
      _log('withdrawal', amount);
      return this;
    },
    getBalance()  { return _balance; },
    getHistory()  { return [..._history]; }, // defensive copy
  };
})();

BankAccount.deposit(500).deposit(200).withdraw(100);
console.log(BankAccount.getBalance()); // 600
console.log(BankAccount._balance);     // undefined — truly private

Real-World Use Case:

Before ES modules became ubiquitous, large libraries like jQuery, Lodash, and early Angular 1.x used the Module Pattern (IIFE + closure) to avoid polluting the global namespace and to encapsulate internal state. Today, ES modules provide file-level encapsulation, but the closure-based pattern is still used inside modules for class-alternative encapsulation and for factories returned from hooks or composables.

Q. What is a closure memory leak and how do you prevent it?

A closure memory leak occurs when a closure unintentionally keeps a large object (such as a DOM node, a large dataset, or a complete HTTP response) in memory by holding a reference in its closed-over scope long after the object is needed. The V8 garbage collector cannot reclaim the memory because the closure is still reachable.

Common patterns that cause leaks:

  1. Event listeners not removed — the listener closes over component state, keeping the entire component tree alive.
  2. Timers not cleared — setInterval callbacks closing over large objects run indefinitely.
  3. Detached DOM nodes — a closure holds a reference to a DOM node that has been removed from the document.
//  Leak: interval callback closes over largeData forever
function startPolling() {
  const largeData = new Array(100_000).fill('payload');

  const id = setInterval(() => {
    console.log(largeData.length); // largeData is never freed
  }, 1000);

  // Missing: return id so the caller can clearInterval(id)
}

//  Fix: return cleanup, or null out the reference
function startPollingFixed() {
  const largeData = new Array(100_000).fill('payload');

  const id = setInterval(() => {
    process(largeData);
  }, 1000);

  return () => clearInterval(id); // caller must invoke this to free memory
}

const stopPolling = startPollingFixed();
// Later, when no longer needed:
stopPolling(); // interval cleared, largeData eligible for GC

React-specific pattern:

useEffect(() => {
  const controller = new AbortController();

  fetch('/api/data', { signal: controller.signal })
    .then(res => res.json())
    .then(setData);

  return () => controller.abort(); // cleanup prevents stale closure + memory leak
}, []);

Real-World Use Case:

In Single Page Applications (SPAs), route navigation causes component unmounting. Event listeners or WebSocket message handlers registered on mount but not removed on unmount keep the entire component closure alive for the application's lifetime, causing memory growth that crashes long-running sessions (e.g., trading dashboards, kiosk apps). React's useEffect cleanup function pattern was designed specifically to address this.

Q. Explain the IIFE pattern and when you would use it over ES modules.

An Immediately Invoked Function Expression (IIFE) is a function that is defined and called in a single expression. Its primary purpose is to create an isolated scope, preventing variable declarations from leaking into the surrounding (often global) scope.

(function() { /* body */ })();
// or with arrow functions:
(() => { /* body */ })();

When to still use IIFEs over ES modules:

  • Browser script tags without a bundler — when you cannot use type="module" and need scope isolation.
  • Polyfill / library UMD bundles — output format used by tools like Rollup/Webpack to expose libraries as window.MyLib while avoiding global pollution.
  • Initialisation code that must run exactly once and should not be importable.
  • Micro-optimisation in hot paths — in rare engine-specific scenarios, an IIFE can help the optimiser treat a block as a separate compilation unit.

Example:

// UMD wrapper (simplified) — the output of many bundlers
(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined'
    ? module.exports = factory()                    // CommonJS (Node.js)
    : typeof define === 'function' && define.amd
      ? define(factory)                             // AMD (RequireJS)
      : (global.MyLib = factory());                 // Browser global
}(typeof globalThis !== 'undefined' ? globalThis : this, function () {
  'use strict';

  const VERSION = '1.0.0';        // private
  return { VERSION, greet: (name) => `Hello, ${name}` }; // public API
}));

Real-World Use Case:

Webpack wraps each module in an IIFE in its legacy output mode to simulate module scope in environments lacking native ES module support (Internet Explorer, older Node.js). When targeting modern environments, Webpack and Rollup emit native ES modules instead, removing the IIFE overhead. Understanding this is essential when configuring output.libraryTarget in webpack configs for distributing npm packages.

Q. How does the scope chain work during variable lookup, and what is the performance implication?

When a variable is referenced, the JavaScript engine performs a scope chain lookup: it starts at the current scope's environment record and walks up through enclosing records until the variable is found or the global scope is exhausted.

V8 optimises this through:

  1. Hidden classes / shapes — objects have stable layouts; property access is compiled to a direct memory offset.
  2. Inline caching (IC) — after the first lookup, V8 caches the resolved location. Repeated access to the same variable from the same call site is O(1).
  3. Closure context allocation — V8's Turbofan compiler performs context allocation analysis. Variables that are only used locally stay on the stack (fast). Variables that are closed over are promoted to a heap-allocated context object — slightly slower due to heap allocation and GC pressure.

Performance implication: deeply nested closures that close over many large variables create long-lived heap contexts. Flattening the closure chain or copying only the needed values can improve GC throughput.

Example:

//  Unnecessarily deep scope chain — engine must walk up 3 levels on each access
function level1() {
  const config = loadHeavyConfig(); // heap-allocated context (closed over)

  function level2() {
    function level3() {
      return config.timeout; // 3-level chain lookup
    }
    return level3;
  }
  return level2;
}

//  Flatten: extract only needed value to the immediate scope
function level1Optimised() {
  const { timeout } = loadHeavyConfig(); // only close over what is needed

  function level2() {
    function level3() {
      return timeout; // 1 var, still 3-level chain but smaller context object
    }
    return level3;
  }
  return level2;
}

Real-World Use Case:

In Node.js request handlers that process millions of requests per minute (e.g., an Express.js API gateway), middleware functions close over the app config object. Profiling with --prof (V8 tick profiler) or Chrome DevTools' flame graph reveals when heap contexts from closures apply GC pressure. The optimisation is to destructure only required config fields at the module level — keeping the closure context objects small and promoting faster minor GC cycles.

Q. What is the difference between scope and context (this) in JavaScript?

These two concepts are frequently confused but are entirely independent mechanisms:

ScopeContext (this)
Determined byWhere code is written (lexical)How a function is called (dynamic)
StoresVariable bindingsThe object a function is operating upon
Changed byImpossible at runtimecall, apply, bind, arrow functions, class fields
Applies toAll identifiersOnly the this keyword

Arrow functions are the intersection: they do not create their own this binding — they inherit this lexically from the enclosing scope at definition time. This makes them behave like closures for this.

Example:

const timer = {
  label: 'Countdown',
  seconds: 3,

  //  Regular function: `this` is determined by caller (lost in setTimeout callback)
  startBroken() {
    setTimeout(function () {
      console.log(this.label); // undefined — `this` is global/undefined (strict mode)
    }, 1000);
  },

  //  Arrow function: `this` inherited lexically from startFixed\'s scope
  startFixed() {
    setTimeout(() => {
      console.log(this.label); // 'Countdown' 
    }, 1000);
  },

  //  Explicit bind alternative
  startWithBind() {
    setTimeout(function () {
      console.log(this.label); // 'Countdown' 
    }.bind(this), 1000);
  },
};

timer.startFixed();

Real-World Use Case:

React class components historically required .bind(this) in the constructor for event handlers because a regular function passed as a JSX prop loses its this binding. The shift to arrow function class fields (handleClick = () => { ... }) and then to functional components with hooks eliminated this entire category of this-related bugs — demonstrating that understanding the scope vs. context distinction is foundational to making informed decisions about component architecture.

# 11. Hoisting


Q. What is hoisting in JavaScript, and what gets hoisted?

Hoisting is a JavaScript mechanism where variable, function, class, and import declarations are conceptually moved to the top of their containing scope during the compilation phase, before the code executes. Hoisting allows functions to be safely used in code before they are declared.

Example 01: Variable Hoisting

console.log(message); // output: undefined
var message = "The variable Has been hoisted";

The above code looks like as below to the interpreter,

var message;
console.log(message);
message = "The variable Has been hoisted";

Example 02: let and const hoisting

All declarations (function, var, let, const and class) are hoisted in JavaScript, while the var declarations are initialized with undefined, but let and const declarations remain uninitialized.

console.log(x);
let x = 10;

// Output: ReferenceError: x is not defined

Example 03: Function Hoisting

One of the advantages of hoisting is that it lets you use a function before you declare it in your code.

getName("Sadhika Sandal");

function getName(name) {
  console.log("Hello " + name);
}

Output:

Hello Sadhika Sandal

They will only get initialized when their lexical binding (assignment) is evaluated during runtime by the JavaScript engine. This means we can't access the variable before the engine evaluates its value at the place it was declared in the source code. This is what we call Temporal Dead Zone (TDZ), A time span between variable creation and its initialization where they can't be accessed.

What gets hoisted and how:

DeclarationHoisted?Initialised to
varYesundefined
letYes (TDZ)uninitialised — ReferenceError if accessed before declaration
constYes (TDZ)uninitialised — ReferenceError if accessed before declaration
function declarationYesFull function body
function expression (var f = function(){})Partiallyvar f hoisted as undefined; body is not hoisted
class declarationYes (TDZ)uninitialised — ReferenceError if accessed before declaration
importYesFully evaluated before module body runs

Real-World Use Case:

Node.js module systems and build tools like Webpack rely on function-declaration hoisting to allow modules to call utility functions defined later in the same file — enabling a readable "public API at the top, implementation details below" code style. However, misunderstanding var hoisting is responsible for a large class of production bugs in legacy jQuery/Angular 1 codebases, which is why the Airbnb ESLint config bans var and mandates let/const.

Q. In which case the function definition is not hoisted in JavaScript?

Let us take the following function expression

var foo = function foo() {
  return 12;
}

In JavaScript var-declared variables and functions are hoisted. Let us take function hoisting first. Basically, the JavaScript interpreter looks ahead to find all the variable declaration and hoists them to the top of the function where It is declared. For example:

foo(); // Here foo is still undefined
var foo = function foo() {
  return 12;
};

The code above behind the scene look something like this:

var foo = undefined;
foo(); // Here foo is undefined
foo = function foo() {
  // Some code stuff
}
var foo = undefined;
foo = function foo() {
  // Some code stuff
}
foo(); // Now foo is defined here

Q. How does hoisting behave differently for function declarations versus function expressions and arrow functions?

  • Function declaration (function foo() {}): The entire function — name and body — is hoisted. The function is callable anywhere in its scope, even before the line it appears on.
  • Function expression (const foo = function() {}): Only the variable declaration is hoisted (if var); the assignment — the function value itself — is not. Calling it before the line throws TypeError (not ReferenceError) because the variable exists but holds undefined.
  • Arrow function (const foo = () => {}): Same as function expression — it is always assigned to a variable, so the same rules apply.

Example:

// 1. Function Declaration — fully hoisted 
console.log(add(2, 3)); // 5

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

// 2. Function Expression with var — var hoisted as undefined 
console.log(multiply); // undefined
// console.log(multiply(2, 3)); // TypeError: multiply is not a function

var multiply = function (a, b) {
  return a * b;
};

// 3. Function Expression with let — TDZ 
// console.log(divide(10, 2)); // ReferenceError
const divide = (a, b) => a / b;

// 4. Practical gotcha: conditional function declaration (avoid — browser-dependent)
if (true) {
  function conditionalFn() { return 'inside if'; }
}
// Behaviour in strict mode: conditionalFn is block-scoped to the if-block
// Behaviour in sloppy mode: varies by browser — avoid this pattern entirely

Real-World Use Case:

In large React component files, developers often define helper arrow functions (const formatDate = (d) => ...) after the component function that uses them. Because the component is a const arrow function itself, calling formatDate inside it only works if formatDate is defined above — otherwise a ReferenceError occurs at module load time. Understanding hoisting rules guides file organisation: place utility declarations before their consumers, or use function declarations when ordering flexibility matters.

Q. How does hoisting interact with the class declaration?

Class declarations are hoisted to the top of their block scope — like let and const — but they are not initialised until the declaration is evaluated. This means they sit in the Temporal Dead Zone (TDZ) and accessing a class before its declaration throws a ReferenceError, not a TypeError. This is a deliberate choice to prevent confusing inheritance hierarchies from being constructed before base classes are fully defined.

Example:

//  Class in TDZ — accessing before declaration throws ReferenceError
// const p = new Person('Alice'); // ReferenceError: Cannot access 'Person' before initialization

class Person {
  constructor(name) {
    this.name = name;
  }
  greet() {
    return `Hi, I am ${this.name}`;
  }
}

const p = new Person('Alice'); // 
console.log(p.greet());        // 'Hi, I am Alice'

// Class expression — same as function expression: name is in TDZ if const/let
const Animal = class {
  constructor(species) {
    this.species = species;
  }
};

const dog = new Animal('Canis lupus'); // 

Practical implication — class vs. function constructor:

// Function constructor: hoisted entirely — can be used before declaration
const f = new Foo(); // 
function Foo() { this.x = 1; }

// Class: TDZ — cannot be used before declaration
// const b = new Bar(); //  ReferenceError
class Bar { constructor() { this.x = 1; } }

Real-World Use Case:

When splitting large Node.js services into files with circular require() references, class-based modules that are accessed during the require() cycle before the class declaration evaluates throw ReferenceError. Understanding the TDZ for classes helps engineers design module load order carefully — or avoid circular dependencies by refactoring to dependency injection patterns (e.g., passing the class as a parameter rather than importing it at the module level).

Q. What is the hoisting behaviour inside let/const blocks, and how does it affect switch statements?

let and const are block-scoped and hoisted within their block to the TDZ. A switch statement's {} is a single block — all case clauses share the same block scope. This causes a subtle bug: a let declared in one case is technically in scope (hoisted) for all other cases in the same switch block, causing a SyntaxError if another case attempts to declare the same name.

Example:

//  SyntaxError: Identifier 'result' has already been declared
function getLabel(status) {
  switch (status) {
    case 'active':
      let result = 'User is active';   // hoisted to top of switch block
      return result;
    case 'inactive':
      let result = 'User is inactive'; // SyntaxError — same block, same name
      return result;
  }
}

//  Fix 1: wrap each case in its own block
function getLabelFixed(status) {
  switch (status) {
    case 'active': {
      let result = 'User is active';
      return result;
    }
    case 'inactive': {
      let result = 'User is inactive'; // own block — no conflict
      return result;
    }
    default:
      return 'Unknown';
  }
}

//  Fix 2: prefer object lookup over switch for simple mappings
const STATUS_LABELS = {
  active:   'User is active',
  inactive: 'User is inactive',
};
const getLabel2 = (status) => STATUS_LABELS[status] ?? 'Unknown';

Real-World Use Case:

This exact bug has appeared in production Redux reducers where developers switched from var to let during a linting migration. All case branches in a reducer's switch share one block, so re-declaring let draft in multiple cases causes a SyntaxError caught only at parse time — blocking the entire app from loading. The standard Redux Toolkit solution is to use Immer-based createSlice instead, which replaces the switch reducer pattern entirely.

Q. How does hoisting affect the execution order of multiple var declarations and function declarations with the same name?

When multiple var declarations or function declarations share the same identifier in the same scope, the engine applies specific precedence rules during the compilation phase:

  1. var declarations are merged into one — only one undefined slot is allocated; whichever assignment executes last wins at runtime.
  2. Function declarations override var declarations of the same name during hoisting — the function body wins over var's undefined.
  3. Multiple function declarations with the same name: the last one in source order wins (it overwrites the earlier one during hoisting).

Example:

// What does the programmer see:
var x = 1;
function x() { return 'fn'; }
var x = 2;

// What the engine processes during compilation:
// 1. var x → undefined (registered)
// 2. function x → x = function() { return 'fn'; } (overwrites var)
// 3. (execution phase) x = 1
// 4. (execution phase) x = 2
console.log(x); // 2 — last assignment wins

// Multiple function declarations — last one wins
function greet() { return 'Hello'; }
function greet() { return 'Hi'; }   // overwrites the first
console.log(greet()); // 'Hi'

// var + function same name — function hoisted OVER var during compilation
console.log(typeof answer); // 'function' (not 'undefined')
var answer = 42;
function answer() { return 'forty-two'; }
console.log(answer); // 42 — runtime assignment overrides the function

Why this matters architecturally:

In large legacy codebases (pre-modules), concatenating multiple script files together frequently caused silent name collisions of this kind. Two files each declaring var config = ... or function init() {} would silently overwrite each other. The last file in the bundle always won, causing intermittent and environment-specific bugs that only appeared in production where file concatenation order differed from the development setup.

Real-World Use Case:

This collision class of bugs was a primary driver for the adoption of the Module Pattern (IIFE-based), then AMD/CommonJS, and ultimately ES modules — each solution adding a layer of scope isolation to prevent hoisting-based name collisions. Modern bundlers (Webpack, Rollup) assign each module its own scope by wrapping it, making cross-module var collision impossible. Understanding this history explains why "use strict" and ES modules are defaults in all modern toolchains.

# 12. ES6 Features


Q. How do arrow functions differ from regular functions, and when should you not use them?

Arrow functions (=>) introduced in ES2015 are a concise syntax for writing functions, but they are not simply shorthand for function. They have fundamental semantic differences:

FeatureRegular FunctionArrow Function
this bindingDynamic — determined by call siteLexical — inherits from enclosing scope
arguments objectAvailableNot available (use rest params)
new (constructor)Can be usedThrows TypeError
prototype propertyHas oneundefined
super / new.targetOwn bindingInherited from enclosing scope
Method shorthandUsable (with caution)Avoid for object methods needing this

When NOT to use arrow functions:

  1. Object methods that need dynamic this (obj.method() — arrow's this is the enclosing scope, not obj).
  2. Constructor functions — arrow functions cannot be called with new.
  3. Event handlers that need this to refer to the DOM element (element.addEventListener('click', () => {}) — this is the outer context, not the element).
  4. Generator functions — arrow syntax does not support function*.
  5. Functions that need their own arguments object.
// --- Concise bodies ---
const double  = x => x * 2;              // single param, expression body
const add     = (a, b) => a + b;         // multiple params
const toUpper = str => ({ upper: str.toUpperCase() }); // returning an object literal — wrap in ()

// --- `this` lexical binding ---
class Timer {
  constructor() {
    this.ticks = 0;
  }

  start() {
    // Arrow function: `this` is the Timer instance (lexical)
    this.interval = setInterval(() => {
      this.ticks++;
      console.log(this.ticks);
    }, 1000);
  }

  stop() {
    clearInterval(this.interval);
  }
}

// --- When NOT to use arrow ---
const counter = {
  count: 0,
  //  Arrow: `this` is the module/global scope, not counter
  incrementBad: () => { counter.count++; },
  //  Method shorthand: `this` is counter
  incrementGood() { this.count++; },
};

//  Arrow as constructor
// const p = new (() => {}); // TypeError: (intermediate value) is not a constructor

//  No own `arguments`
const fn = () => {
  // console.log(arguments); // ReferenceError in strict mode / module scope
};
const fn2 = (...args) => args; //  Use rest params instead

Real-World Use Case:

React's functional components and hooks use arrow functions almost exclusively for event handlers passed as JSX props (onClick={() => dispatch(action)}). Because these arrow functions inherit this from the component scope (which in a functional component is irrelevant — there is no this), the lexical binding is harmless and the concise syntax reduces boilerplate. Conversely, in Express.js route handlers or Mongoose middleware that rely on this referring to the document/request, using arrow functions is a common production bug — the official Mongoose docs explicitly warn against it.

Q. What are template literals, and what are their advanced capabilities beyond string interpolation?

Template literals (backtick strings) introduced in ES2015 offer four key capabilities over traditional string literals:

  1. String interpolation — embed any expression with ${expr}.
  2. Multi-line strings — newlines are literal; no \n needed.
  3. Expression evaluation — any valid JavaScript expression inside ${} including ternaries, function calls, and arithmetic.
  4. Tagged templates — a function (tag) is called with the string parts and interpolated values, enabling domain-specific languages (DSLs), sanitisation, and localisation.
// 1. Interpolation
const user = { name: 'Alice', score: 42 };
console.log(`Player: ${user.name}, Score: ${user.score * 2}`); // Player: Alice, Score: 84

// 2. Multi-line
const html = `
  <article>
    <h1>${user.name}</h1>
    <p>Score: ${user.score}</p>
  </article>
`.trim();

// 3. Expressions inside ${}
const items = ['apple', 'banana', 'cherry'];
console.log(`You have ${items.length} item${items.length !== 1 ? 's' : ''}.`);
// 'You have 3 items.'

// 4. Tagged template literals
function highlight(strings, ...values) {
  return strings.reduce((result, str, i) =>
    result + str + (values[i] !== undefined ? `<mark>${values[i]}</mark>` : ''),
    ''
  );
}

const product = 'JavaScript';
const year = 2015;
console.log(highlight`${product} was standardised in ${year}.`);
// '<mark>JavaScript</mark> was standardised in <mark>2015</mark>.'

// SQL injection prevention with tagged templates (concept)
function sql(strings, ...values) {
  const sanitised = values.map(v => sanitiseInput(v)); // escape each interpolated value
  return strings.reduce((q, str, i) => q + str + (sanitised[i] ?? ''), '');
}
function sanitiseInput(v) { return String(v).replace(/'/g, "''"); }

const userId = "1' OR '1'='1"; // SQL injection attempt
const query = sql`SELECT * FROM users WHERE id = '${userId}'`;
// SELECT * FROM users WHERE id = '1'' OR ''1''=''1'  — safely escaped

Real-World Use Case:

The styled-components and emotion CSS-in-JS libraries are built entirely on tagged template literals. When you write styled.div`color: ${props => props.primary ? 'blue' : 'grey'};`, the library's tag function receives the static CSS parts and the dynamic value functions, generates a unique class name, injects a <style> rule, and tracks theme changes — all without any custom parser. This demonstrates that tagged templates can implement fully featured DSLs (CSS, GraphQL, SQL, i18n) natively in JavaScript with zero dependencies.

Q. Explain destructuring assignment — object, array, nested, and with defaults.

Destructuring is a syntax for unpacking values from arrays or properties from objects into distinct variables in a single statement. It is purely syntactic sugar — the engine converts it to a series of property accesses and assignments.

// ── Object destructuring ──────────────────────────────────────────────────────

const user = { id: 1, name: 'Alice', role: 'admin', address: { city: 'NYC', zip: '10001' } };

// Basic
const { id, name } = user;

// Rename (alias)
const { name: username, role: userRole } = user;
console.log(username, userRole); // 'Alice', 'admin'

// Default values (used when property is undefined)
const { theme = 'light', lang = 'en' } = user;
console.log(theme, lang); // 'light', 'en'

// Nested destructuring
const { address: { city, zip } } = user;
console.log(city, zip); // 'NYC', '10001'

// Rest in object destructuring
const { id: userId, ...rest } = user;
console.log(userId); // 1
console.log(rest);   // { name: 'Alice', role: 'admin', address: {...} }

// ── Array destructuring ───────────────────────────────────────────────────────

const rgb = [255, 128, 0];

const [red, green, blue] = rgb;
const [r, , b] = rgb;    // skip elements with empty commas
const [first, ...others] = rgb; // rest in array

// Swap variables without temp
let a = 1, bVal = 2;
[a, bVal] = [bVal, a];
console.log(a, bVal); // 2, 1

// Default values
const [x = 0, y = 0, z = 0] = [10, 20];
console.log(x, y, z); // 10, 20, 0

// ── Function parameter destructuring ─────────────────────────────────────────

function renderCard({ title, subtitle = 'No subtitle', tags = [] } = {}) {
  return `<h1>${title}</h1><h2>${subtitle}</h2><ul>${tags.map(t => `<li>${t}</li>`).join('')}</ul>`;
}

renderCard({ title: 'Hello', tags: ['js', 'es6'] });
renderCard(); //  uses defaults — the `= {}` prevents TypeError when called with no args

// ── Mixed / deeply nested ─────────────────────────────────────────────────────

const apiResponse = {
  data: {
    users: [
      { id: 1, profile: { avatar: 'alice.png' } },
      { id: 2, profile: { avatar: 'bob.png' } },
    ],
  },
};

const { data: { users: [{ id: firstId, profile: { avatar: firstAvatar } }] } } = apiResponse;
console.log(firstId, firstAvatar); // 1, 'alice.png'

Real-World Use Case:

React's useState hook returns a two-element array precisely to enable array destructuring with custom naming: const [count, setCount] = useState(0). This pattern lets each call site choose its own variable names without the library needing to know them. Similarly, React component props are universally destructured in the function signature (function Button({ label, onClick, disabled = false }) {}), reducing prop-drilling verbosity and making prop contracts immediately visible — a pattern enforced by most team style guides and ESLint configs.

Q. What is the difference between the rest parameter and the spread operator?

Both use the ... syntax but serve opposite purposes:

Rest (...params)Spread (...iterable)
PositionLast parameter in a function signatureCall site, array literal, or object literal
PurposeCollects multiple arguments into an arrayExpands an iterable/object into individual elements
ResultOne ArrayMultiple values
// ── Rest parameter ───────────────────────────────────────────────────────────

function sum(...numbers) {           // collects all args into an array
  return numbers.reduce((acc, n) => acc + n, 0);
}
console.log(sum(1, 2, 3, 4)); // 10

function log(level, ...messages) {   // first arg fixed, rest collected
  console[level](messages.join(' '));
}
log('warn', 'Disk', 'usage', 'high'); // console.warn('Disk usage high')

// ── Spread operator ──────────────────────────────────────────────────────────

// 1. Expanding arrays
const nums = [1, 2, 3];
const more = [...nums, 4, 5];        // [1, 2, 3, 4, 5]
const copy = [...nums];              // shallow copy

// 2. Spreading into a function call
console.log(Math.max(...nums));      // 3

// 3. Merging / cloning objects (shallow)
const defaults = { theme: 'light', lang: 'en', debug: false };
const userPrefs = { theme: 'dark', fontSize: 14 };
const config = { ...defaults, ...userPrefs }; // right side wins on key collision
// { theme: 'dark', lang: 'en', debug: false, fontSize: 14 }

// 4. Spreading a string into characters
const chars = [..."hello"]; // ['h', 'e', 'l', 'l', 'o']

// 5. Converting NodeList / Set to Array
const unique = [...new Set([1, 1, 2, 3, 3])]; // [1, 2, 3]

Real-World Use Case:

Redux reducers use object spread to produce new state immutably without mutating the existing state object — a requirement of Redux's change-detection mechanism: return { ...state, isLoading: false, data: action.payload }. This replaces Object.assign({}, state, { isLoading: false }) with far more readable syntax. The V8 engine optimises spread-based object cloning with the same hidden-class mechanisms as Object.assign, so there is no meaningful performance difference in most application code.

Q. Explain ES6 Map and Set — how do they differ from plain objects and arrays, and when should you use them?

Set is a collection of unique values of any type. It maintains insertion order and provides O(1) has() lookup (hash-based internally in V8).

Map is a collection of key-value pairs where keys can be any type (including objects, functions, and NaN). Plain objects only allow string/Symbol keys.

FeatureObjectMap
Key typesString / Symbol onlyAny value (including objects)
OrderedNot guaranteed (though V8 preserves insertion order for string keys in practice)Insertion order guaranteed by spec
SizeObject.keys(obj).length — O(n)map.size — O(1)
Iterationfor...in (includes prototype) / Object.entries()for...of, forEach, direct iterators
Prototype pollutionPossible (e.g., __proto__)Not possible
JSON serialisableYes(needs custom serialiser)
FeatureArraySet
UniquenessNo — duplicates allowedYes — duplicates silently ignored
has / membershipindexOf / includes — O(n)set.has() — O(1)
OrderedIndex-basedInsertion-order
// ── Set ──────────────────────────────────────────────────────────────────────

const visited = new Set();
visited.add('/home');
visited.add('/about');
visited.add('/home');   // duplicate — silently ignored

console.log(visited.size);        // 2
console.log(visited.has('/about')); // true (O(1))

// Deduplicate an array
const tags = ['js', 'css', 'js', 'html', 'css'];
const uniqueTags = [...new Set(tags)]; // ['js', 'css', 'html']

// ── Map ──────────────────────────────────────────────────────────────────────

const cache = new Map();

// Object as key (impossible with plain objects as keys)
const req1 = { url: '/api/users' };
const req2 = { url: '/api/posts' };

cache.set(req1, { data: [1, 2, 3], ts: Date.now() });
cache.set(req2, { data: ['a', 'b'], ts: Date.now() });

console.log(cache.get(req1).data); // [1, 2, 3]
console.log(cache.size);           // 2

// Iteration
for (const [key, value] of cache) {
  console.log(key.url, value.data);
}

// Convert to / from plain object
const mapFromObj = new Map(Object.entries({ a: 1, b: 2 }));
const objFromMap = Object.fromEntries(mapFromObj);

Real-World Use Case:

WeakMap (a variant of Map with weak references and object-only keys) is used by React's Concurrent Mode internals to associate component fiber metadata with DOM nodes without preventing garbage collection of unmounted components. Set is used in many state management libraries (Zustand, Jotai) to track subscriber callbacks: O(1) add/delete/has operations make subscription management significantly faster than array-based alternatives at scale (thousands of subscribers in large dashboards).

Q. What are default parameters in ES6, and how do they interact with undefined vs. null?

ES6 default parameters allow function parameters to have a fallback value used when the argument is undefined or when no argument is provided. Crucially, passing null does not trigger the default — null is an explicit value meaning "no object here", while undefined means "value not provided".

Default expressions are evaluated lazily — they run each time the function is called with a missing argument, not once at function definition time. This allows defaults that reference other parameters or call functions.

// Basic defaults
function createUser(name, role = 'viewer', createdAt = new Date()) {
  return { name, role, createdAt };
}

createUser('Alice');                     // role: 'viewer', createdAt: <now>
createUser('Bob', 'admin');             // role: 'admin'
createUser('Carol', undefined, new Date('2024-01-01')); // role: 'viewer' (undefined triggers default)
createUser('Dave', null);              // role: null — null does NOT trigger default 

// Default referencing earlier parameter
function range(start, end = start + 10) {
  return Array.from({ length: end - start }, (_, i) => start + i);
}
console.log(range(5));    // [5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
console.log(range(1, 4)); // [1, 2, 3]

// Default calling a function (evaluated per call)
let callCount = 0;
function getDefault() { return ++callCount; }

function track(id = getDefault()) { 

Truncated — view the full README on GitHub.

es6-interview-questions
javascript
javascript-for-beginners
javascript-interview-questions
javascript-tutorials

Significant stargazers

Bryan C Guner

339 followers · starred Feb 2022

Dylan Cutler

248 followers · starred Apr 2021

Vinay Arvind Badgujar

19 followers · starred Aug 2024

Rishika Ghosh

168 followers · starred Aug 2021