0% found this document useful (0 votes)
329 views

JavaScript Final V1

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
329 views

JavaScript Final V1

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 787

1

Table of Content

Chapter 1: JavaScript Fundamentals ..................................................................................................................... 6


• Basics & Syntax:
o Variables (var, let, const)
o Data Types (string, number, boolean, null, undefined, symbol, bigint)
o Operators (arithmetic, assignment, comparison, logical, bitwise)
• Control Structures:
o Conditional Statements (if-else, switch)
o Loops (for, while, do-while, for...in, for...of)
o Labels and Break/Continue
• Functions and Scope:
o Function Declarations vs. Expressions
o Arrow Functions
o Anonymous Functions & IIFE (Immediately Invoked Function Expressions)
o Callback Functions and Higher-Order Functions
o Scope (Global, Local, Block)
o Closures and Lexical Scope

Chapter 2: DOM Manipulation & Browser APIs .............................................................................................. 63


• DOM (Document Object Model):
o Selecting Elements (getElementById, querySelector, etc.)
o Modifying Elements (innerHTML, classList, style)
o Event Handling (onClick, addEventListener, Event Delegation)
• BOM (Browser Object Model):
o Window and Document Objects
o Working with LocalStorage, SessionStorage, Cookies
o Fetch API and AJAX Calls
o Timers (setTimeout, setInterval)
• Events and Event Propagation:
o Event Bubbling and Capturing
o Default Events and Preventing Default Behavior

Chapter 3: Object-Oriented JavaScript (OOP) .............................................................................................. 120


• Objects and Prototypes:
o Object Literals and Constructors
o Prototypes and Prototype Inheritance
o Object.create() and Prototype Chain
• Classes and Inheritance:
o Class Syntax and Constructors
o Inheritance (extends, super)
o Static Methods and Getters/Setters
• This Keyword and Bindings:
o Implicit and Explicit Binding (call, apply, bind)

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
2

o Arrow Functions and Lexical ‘this’

Chapter 4: Asynchronous JavaScript ................................................................................................................. 182


• Callbacks:
o Handling Asynchronous Code using Callbacks
• Promises:
o Promise Creation, Chaining, and Error Handling
o Using .then() and .catch()
• Async/Await:
o Writing Asynchronous Code with Async Functions
o Error Handling in Async/Await
• Event Loop and Task Queue:
o Microtasks vs. Macrotasks
o Understanding the Call Stack and Event Loop
o JavaScript’s Single-Threaded Nature

Chapter 5 : Error Handling and Debugging ................................................................................................. 244


• Types of Errors:
o Syntax Errors, Runtime Errors, and Logical Errors
• Exception Handling:
o Try-Catch Blocks
o Throwing Errors Manually
o Custom Errors and Error Objects
• Debugging Techniques:
o Using Browser Developer Tools
o Console Methods (console.log, warn, error, group, table)

Chapter 6 : JavaScript ES6+ Features (Modern JavaScript) ................................................................ 300


• New Syntax and Features:
o Destructuring (Arrays and Objects)
o Spread and Rest Operators
o Default Parameters
o Template Literals
• Iterators and Generators:
o Understanding for...of Loops
o Creating and Using Generators
• Modules:
o Export and Import Syntax
o Dynamic Imports
o Differences between CommonJS and ES Modules
• Symbol and Set/Map:
o Usage of Symbol Data Type
o Sets, Maps, WeakSets, and WeakMaps

Chapter 7 : Advanced JavaScript Concepts ...................................................................................................353

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
3

• Currying and Partial Functions


• Memoization and Throttling/Debouncing
• Design Patterns in JavaScript:
o Singleton, Observer, Factory, Module Patterns
• Functional Programming Concepts:
o Pure Functions, Immutability, Function Composition
o Map, Filter, Reduce Methods

Chapter 8 : JavaScript Frameworks and Libraries ......................................................................................410


• React.js:
o Components, JSX, State and Props
o React Hooks (useState, useEffect, useReducer)
o React Router and Context API
• Vue.js:
o Data Binding, Directives, Components
o Vue CLI and Vuex (State Management)
• Node.js:
o Event-driven Server-side JavaScript
o Working with Express.js
o File Handling and Middleware
• Angular:
o Two-way Data Binding, Components, and Services
o Dependency Injection

Chapter 9: Testing JavaScript Code ................................................................................................................... 468


• Unit Testing with Jest and Mocha
• Integration Testing
• End-to-End Testing (E2E) with Cypress
• Debugging Test Cases using Chrome DevTools

Chapter 10: New Trends & Technologies in JavaScript (2024) ........................................................... 536
• Serverless Architecture:
o AWS Lambda, Google Cloud Functions
• Edge Computing with JavaScript:
o Deno, Cloudflare Workers
• JavaScript in IoT Development
• Artificial Intelligence and Machine Learning with JavaScript:
o TensorFlow.js and Brain.js
• WebAssembly and JavaScript Interoperability
• Progressive Web Apps (PWAs)
• Modern Frontend Tools:
o Vite, TurboRepo, Next.js for React, SvelteKit

Chapter 11: Security in JavaScript ......................................................................................................................... 614

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
4

• Common JavaScript Vulnerabilities:


o Cross-Site Scripting (XSS)
o Cross-Site Request Forgery (CSRF)
o SQL Injection Prevention
• Securing APIs and Tokens:
o JWT (JSON Web Tokens) Usage
o OAuth and OpenID Connect

Chapter 12: JavaScript in DevOps and Automation .................................................................................. 670


• Using Node.js for Automation:
o Writing Scripts for CI/CD Pipelines
o Building Command Line Interfaces (CLI)
• JavaScript Build Tools:
o Webpack, Rollup, Parcel
• Package Managers:
o npm, Yarn, pnpm

Chapter 13: Interview Preparation: Practice Problems and Coding Questions ........................ 722
• Algorithms and Data Structures with JavaScript:
o Array and String Manipulation
o Recursion Problems
o Sorting Algorithms (Bubble, Merge, Quick Sort)
o Search Algorithms (Binary Search, Linear Search)
o Data Structures (Stacks, Queues, Trees, Graphs)
• System Design Interview Questions:
o Designing Frontend-heavy Applications
o API Design using Express and Node.js

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
5

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
6

Chapter 1: JavaScript Fundamentals

THEORETICAL QUESTIONS

1. What is the difference between var, let, and const in JavaScript?

Answer:
var, let, and const are used to declare variables, but they have different behaviors in terms
of scope and mutability.

● var: Has function scope or global scope. Variables declared with var can be re-
declared and updated.
● let: Has block scope and cannot be re-declared in the same scope. It can, however, be
updated.
● const: Like let, it has block scope, but it cannot be re-assigned after initialization.

For Example:

function example() {
var x = 10;
if (true) {
var x = 20; // re-declares x
console.log(x); // Output: 20
}
console.log(x); // Output: 20
}

let y = 10;
if (true) {
let y = 20; // Creates a new y within this block
console.log(y); // Output: 20
}
console.log(y); // Output: 10

const z = 30;
// z = 40; // Error: Assignment to constant variable

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
7

2. What are the different data types in JavaScript?

Answer:
JavaScript supports seven primitive data types and one non-primitive type (object). These
types include:

1. String: Sequence of characters, e.g., "Hello".


2. Number: Represents numeric values, both integers and floating points.
3. Boolean: Holds true or false.
4. Null: Represents an empty or non-existent value.
5. Undefined: A variable without an assigned value.
6. Symbol: Unique and immutable data type (used for object properties).
7. BigInt: Used to represent integers larger than Number.MAX_SAFE_INTEGER.
8. Object: Non-primitive type used to store collections of data.

For Example:

let a = "Hello"; // String


let b = 42; // Number
let c = true; // Boolean
let d = null; // Null
let e; // Undefined
let f = Symbol('unique'); // Symbol
let g = 9007199254740991n; // BigInt
let h = { name: "Alice" }; // Object
console.log(typeof a, typeof b, typeof c); // Output: string number boolean

3. What are arithmetic operators in JavaScript?

Answer:
Arithmetic operators are used to perform basic mathematical operations on numbers.

● Addition (+): Adds two values.


● Subtraction (-): Subtracts one value from another.
● Multiplication (*): Multiplies two values.
● Division (/): Divides one value by another.
● Modulus (%): Returns the remainder of a division.
● Exponentiation (**): Raises one value to the power of another.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
8

● Increment (++): Increases a variable's value by 1.


● Decrement (--): Decreases a variable's value by 1.

For Example:

let x = 10;
let y = 3;
console.log(x + y); // Output: 13
console.log(x - y); // Output: 7
console.log(x * y); // Output: 30
console.log(x / y); // Output: 3.333...
console.log(x % y); // Output: 1
console.log(x ** y); // Output: 1000
x++;
console.log(x); // Output: 11

4. What is the difference between == and ===?

Answer:
The == operator compares two values for equality, with type coercion (it converts the types if
they are different). On the other hand, the === operator checks both the value and the type
for strict equality.

● ==: Only checks value, allowing type conversion.


● ===: Checks both value and type, with no type conversion.

For Example:

console.log(5 == '5'); // Output: true (type coercion)


console.log(5 === '5'); // Output: false (different types)
console.log(true == 1); // Output: true (1 is considered true)
console.log(true === 1); // Output: false (different types)

5. What is the difference between if-else and switch statements?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
9

Answer:
Both if-else and switch are used for conditional branching, but they differ in syntax and
use case.

● if-else: Best for checking conditions involving logical comparisons.


● switch: More readable when dealing with multiple specific cases for a single variable.

For Example:

let day = 'Monday';


if (day === 'Monday') {
console.log("Start of the week!");
} else if (day === 'Friday') {
console.log("Almost weekend!");
} else {
console.log("It's a regular day.");
}

// Using switch
switch (day) {
case 'Monday':
console.log("Start of the week!");
break;
case 'Friday':
console.log("Almost weekend!");
break;
default:
console.log("It's a regular day.");
}

6. What are loops in JavaScript, and why are they used?

Answer:
Loops are used to execute a block of code repeatedly until a specified condition is met.
JavaScript provides several looping constructs:

● for: A counter-controlled loop.


● while: Executes as long as the condition is true.
● do-while: Executes at least once before checking the condition.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
10

● for...in: Iterates over the properties of an object.


● for...of: Iterates over iterable objects like arrays.

For Example:

// for loop
for (let i = 0; i < 3; i++) {
console.log(i); // Output: 0, 1, 2
}

// while loop
let j = 0;
while (j < 3) {
console.log(j); // Output: 0, 1, 2
j++;
}

7. What is the difference between function declarations and function


expressions?

Answer:
A function declaration is hoisted to the top of its scope, meaning it can be called before its
definition. A function expression is not hoisted and must be declared before use.

For Example:

// Function Declaration
function greet() {
console.log("Hello!");
}
greet(); // Output: Hello!

// Function Expression
const greetExpression = function() {
console.log("Hello from expression!");
};
greetExpression(); // Output: Hello from expression!

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
11

8. What are arrow functions in JavaScript?

Answer:
Arrow functions provide a more concise syntax for writing functions. They have implicit this
binding, meaning they do not have their own this context and instead inherit from their
parent scope.

For Example:

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


console.log(add(2, 3)); // Output: 5

const greet = name => console.log(`Hello, ${name}!`);


greet("Alice"); // Output: Hello, Alice!

9. What are closures in JavaScript?

Answer:
A closure is a function that retains access to its lexical scope even when the function is
executed outside of its original context.

For Example:

function outerFunction(outerVariable) {
return function innerFunction(innerVariable) {
console.log(`Outer: ${outerVariable}, Inner: ${innerVariable}`);
};
}
const closure = outerFunction('outside');
closure('inside'); // Output: Outer: outside, Inner: inside

10. What is the difference between for...in and for...of loops?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
12

Answer:

● for...in: Iterates over the properties of an object.


● for...of: Iterates over the values of an iterable (like arrays or strings).

For Example:

const obj = { a: 1, b: 2 };
for (let key in obj) {
console.log(key); // Output: a, b
}

const arr = [10, 20, 30];


for (let value of arr) {
console.log(value); // Output: 10, 20, 30
}

11. What are the different types of functions in JavaScript?

Answer:
JavaScript offers various types of functions to fit different programming scenarios:

● Named Functions: These functions have a specific name and are useful when
reusability is required.
● Anonymous Functions: Functions without names. They are often used as arguments
to other functions (e.g., callbacks).
● Arrow Functions: Introduced in ES6, they provide a shorter syntax and have no this
binding of their own, making them useful for callbacks and closures.
● IIFE (Immediately Invoked Function Expression): Executes immediately after its
definition, often used to create private scopes and avoid variable conflicts.
● Higher-Order Functions: Functions that take other functions as parameters or return
functions, enabling advanced patterns like composition.

For Example:

// Named Function

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
13

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

// Anonymous Function assigned to a variable


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

// Arrow Function (Shorter syntax)


const multiply = (a, b) => a * b;

// IIFE - Runs immediately


(function() {
console.log("IIFE executed immediately");
})();

// Higher-Order Function example


const applyFunction = (x, func) => func(x);
console.log(applyFunction(5, (n) => n * n)); // Output: 25

12. What is the difference between undefined and null?

Answer:
Both undefined and null represent an absence of value, but they differ in how they are used
and behave.

● undefined: A variable is automatically assigned undefined when it is declared but not


initialized. It also occurs when a function does not explicitly return a value.
● null: It is an assignment value that represents "no value" or "empty". Developers use
null to indicate the intentional absence of a value.

For Example:

let x;
console.log(x); // Output: undefined (x is declared but not assigned)

let y = null;

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
14

console.log(y); // Output: null (y is intentionally set to "no value")


console.log(typeof y); // Output: object (quirk in JavaScript)

function test() {}
console.log(test()); // Output: undefined (function has no return value)

13. What are callback functions in JavaScript?

Answer:
A callback function is a function passed as an argument to another function. It is commonly
used in asynchronous operations such as reading files, API requests, or handling events.
JavaScript is single-threaded, and callbacks help prevent the blocking of code execution by
executing the function after the asynchronous task is completed.

For Example:

function greet(name, callback) {


console.log(`Hello, ${name}`);
callback();
}

function afterGreet() {
console.log("This message runs after the greeting.");
}

greet("Alice", afterGreet);
// Output:
// Hello, Alice
// This message runs after the greeting.

14. What is an Immediately Invoked Function Expression (IIFE)?

Answer:
An IIFE is a function that executes as soon as it is defined. It provides an isolated scope to
avoid polluting the global environment. This is useful when you need to create variables or
functions that should not interfere with other parts of the program.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
15

For Example:

(function() {
let counter = 0;
console.log(`Counter initialized to: ${counter}`);
})();

Here, the variable counter is not accessible outside the IIFE, preserving its privacy.

15. What is the difference between block scope, function scope, and global
scope?

Answer:

● Global Scope: Variables defined outside any function or block can be accessed from
anywhere in the program.
● Function Scope: Variables declared inside a function are accessible only within that
function.
● Block Scope: Variables declared with let or const inside a block (e.g., within {}) are
restricted to that block.

For Example:

let globalVar = "Global"; // Global scope

function demoScope() {
let funcVar = "Function Scope"; // Function scope
console.log(globalVar); // Output: Global
}
// console.log(funcVar); // Error: funcVar is not defined

{
let blockVar = "Block Scope"; // Block scope
console.log(blockVar); // Output: Block Scope
}
// console.log(blockVar); // Error: blockVar is not defined

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
16

16. What is hoisting in JavaScript?

Answer:
Hoisting is a behavior where JavaScript moves declarations (but not initializations) to the top
of their scope. This applies to both variable and function declarations. However, variables
declared with let and const are not accessible before they are declared, due to the
temporal dead zone.

For Example:

console.log(x); // Output: undefined (hoisted declaration)


var x = 5;

// console.log(y); // Error: Cannot access 'y' before initialization


let y = 10;

17. What is the purpose of the this keyword in JavaScript?

Answer:
The this keyword refers to the object it belongs to. In a method, this refers to the object
calling the method. In the global context, this refers to the global object (in browsers, it is
the window object). Arrow functions inherit this from their surrounding scope.

For Example:

const person = {
name: "Alice",
greet: function() {
console.log(`Hello, ${this.name}`);
}
};
person.greet(); // Output: Hello, Alice

const arrowFunc = () => console.log(this);


arrowFunc(); // Output: Window (in browser)

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
17

18. What are default parameters in JavaScript functions?

Answer:
Default parameters allow you to initialize function parameters with default values if no
argument is passed or if the argument is undefined. This makes the code more concise and
eliminates the need for extra checks.

For Example:

function greet(name = "Guest") {


console.log(`Hello, ${name}!`);
}

greet(); // Output: Hello, Guest!


greet("Alice"); // Output: Hello, Alice!

19. What are template literals in JavaScript?

Answer:
Template literals are string literals enclosed by backticks (`) instead of quotes. They allow
embedding variables and expressions using ${}. They also support multi-line strings without
needing escape sequences.

For Example:

let user = "Alice";


let age = 25;

let message = `User: ${user}


Age: ${age}`;
console.log(message);
// Output:
// User: Alice
// Age: 25

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
18

20. What is a higher-order function in JavaScript?

Answer:
A higher-order function is a function that takes other functions as arguments or returns
functions. They allow more modular code and are essential in functional programming,
enabling operations like map, filter, and reduce.

For Example:

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

// Using a higher-order function (map) to double the numbers


const doubled = numbers.map((n) => n * 2);
console.log(doubled); // Output: [2, 4, 6, 8]

// Custom higher-order function example


const applyFunction = (x, func) => func(x);
console.log(applyFunction(5, (n) => n * n)); // Output: 25

Here, applyFunction takes a function as an argument and applies it to the input value.

21. What is the event loop in JavaScript, and how does it work?

Answer:
The event loop manages the execution of asynchronous code in JavaScript. Since JavaScript
is single-threaded, it can only do one thing at a time. The event loop ensures non-blocking
operations by coordinating tasks between:

● Call Stack: Where the currently executed functions are stored.


● Web APIs: Handle time-consuming operations (like timers or network requests).
● Task Queue: Holds tasks to be executed after the call stack is empty.
● Microtask Queue: Contains tasks like resolved promises that run before tasks in the
task queue.

The event loop constantly checks if the call stack is empty, and if so, it moves the next task
from the microtask queue (or task queue) to the call stack.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
19

For Example:

console.log('Start');

setTimeout(() => console.log('Timeout'), 0); // Goes to task queue


Promise.resolve().then(() => console.log('Promise')); // Goes to microtask queue

console.log('End');
// Output: Start, End, Promise, Timeout

Here, the promise runs before setTimeout because microtasks have higher priority than
tasks.

22. What are JavaScript Promises? Explain their states.

Answer:
A promise represents an asynchronous operation's eventual success or failure. It provides a
cleaner alternative to callbacks and avoids callback hell. A promise has three possible states:

1. Pending: The promise is neither fulfilled nor rejected yet.


2. Fulfilled: The operation completed successfully, and the promise returns a result.
3. Rejected: The operation failed, and the promise returns an error.

For Example:

const promise = new Promise((resolve, reject) => {


let success = true;
if (success) resolve('Success!');
else reject('Failed');
});

promise
.then((result) => console.log(result)) // Output: Success!
.catch((error) => console.log(error));

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
20

Promises improve readability by chaining .then() and .catch() to handle success and
failure.

23. What is async/await, and how does it simplify working with Promises?

Answer:
async/await makes asynchronous code easier to read and manage. The async keyword
marks a function as asynchronous, and await pauses the execution of the function until the
promise resolves.

● async functions always return a promise.


● await can only be used inside an async function, and it waits for the promise to settle
before moving to the next line.

For Example:

async function fetchData() {


try {
let response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
let data = await response.json();
console.log(data);
} catch (error) {
console.error('Error fetching data:', error);
}
}

fetchData();

This code avoids deeply nested .then() calls and makes the logic clearer.

24. How does JavaScript handle errors with try-catch?

Answer:
The try-catch block handles runtime errors and prevents them from crashing the program.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
21

Code in the try block is executed, and if an error occurs, it is caught by the catch block. The
finally block runs regardless of whether an error occurs, allowing you to clean up resources.

For Example:

try {
let result = 10 / 0;
console.log(result); // Output: Infinity
let obj = undefined;
console.log(obj.name); // This will throw an error
} catch (error) {
console.error('Caught an error:', error.message);
} finally {
console.log('This runs regardless of errors.');
}
// Output:
// Caught an error: Cannot read properties of undefined (reading 'name')
// This runs regardless of errors.

25. What are modules in JavaScript? Explain import and export.

Answer:
Modules help organize JavaScript code by splitting it into reusable pieces. They prevent
name collisions and improve maintainability. JavaScript uses the import and export
keywords to manage modules.

● export: Exposes variables, functions, or objects to other files.


● import: Imports those exported elements into another file.

For Example:

// module.js
export const greet = (name) => console.log(`Hello, ${name}!`);

// main.js
import { greet } from './module.js';
greet('Alice'); // Output: Hello, Alice!

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
22

26. What is the difference between shallow copy and deep copy in
JavaScript?

Answer:

● Shallow Copy: Copies only the top-level properties of an object, while nested objects
are copied as references.
● Deep Copy: Recursively copies all levels, ensuring no shared references between the
original and the copied object.

For Example:

const obj1 = { a: 1, b: { c: 2 } };
const shallowCopy = { ...obj1 }; // Shallow copy
const deepCopy = JSON.parse(JSON.stringify(obj1)); // Deep copy

shallowCopy.b.c = 42;
console.log(obj1.b.c); // Output: 42 (shallow copy affected original)

deepCopy.b.c = 100;
console.log(obj1.b.c); // Output: 42 (deep copy is independent)

27. Explain the concept of closures with a practical example.

Answer:
A closure allows a function to access variables from its outer scope, even after the outer
function has finished execution. Closures are used to create private variables or maintain
state.

For Example:

function counter() {
let count = 0;
return function () {
count++;
console.log(count);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
23

};
}

const increment = counter();


increment(); // Output: 1
increment(); // Output: 2
increment(); // Output: 3

Here, the inner function retains access to count even after counter() has executed.

28. What are setTimeout and setInterval? How are they different?

Answer:

● setTimeout: Runs a function once after a delay.


● setInterval: Repeatedly runs a function at specified intervals.

For Example:

// setTimeout example
setTimeout(() => console.log('Executed after 2 seconds'), 2000);

// setInterval example
let count = 0;
const interval = setInterval(() => {
count++;
console.log(`Count: ${count}`);
if (count === 3) clearInterval(interval); // Stops after 3 counts
}, 1000);

29. What is the difference between synchronous and asynchronous code in


JavaScript?

Answer:

● Synchronous Code: Blocks the execution until the current task completes.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
24

● Asynchronous Code: Allows other tasks to run while waiting for the current task to
complete.

For Example:

// Synchronous code
console.log('Start');
for (let i = 0; i < 1000000000; i++) {} // Blocking loop
console.log('End');

// Asynchronous code
console.log('Start');
setTimeout(() => console.log('Timeout'), 0); // Non-blocking
console.log('End');
// Output: Start, End, Timeout

Asynchronous code ensures better performance by avoiding blocking.

30. How does the debounce function work, and when would you use it?

Answer:
Debouncing ensures a function is called only after a specified delay since its last invocation. It
is used to limit the rate of function calls, especially during frequent events (like resizing or key
presses).

For Example:

function debounce(func, delay) {


let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), delay);
};
}

const resizeHandler = debounce(() => {


console.log('Resize event handled!');

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
25

}, 500);

window.addEventListener('resize', resizeHandler);

This example limits the resizeHandler call to only once every 500ms, improving
performance.

31. What is the difference between call(), apply(), and bind() in


JavaScript?

Answer:
call(), apply(), and bind() are methods used to control the value of this in JavaScript
functions.

● call(): Invokes a function with a given this value and arguments passed
individually.
● apply(): Similar to call(), but arguments are passed as an array.
● bind(): Returns a new function with this bound to the specified object, without
invoking it immediately.

For Example:

const person = {
name: "Alice",
greet: function (age) {
console.log(`Hello, my name is ${this.name} and I am ${age} years old.`);
}
};

const anotherPerson = { name: "Bob" };

// Using call
person.greet.call(anotherPerson, 25); // Output: Hello, my name is Bob and I am 25
years old.

// Using apply

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
26

person.greet.apply(anotherPerson, [30]); // Output: Hello, my name is Bob and I am


30 years old.

// Using bind
const boundGreet = person.greet.bind(anotherPerson, 35);
boundGreet(); // Output: Hello, my name is Bob and I am 35 years old.

32. What is the difference between map(), filter(), and reduce()?

Answer:
These are higher-order array methods:

● map(): Creates a new array by transforming every element in the original array.
● filter(): Creates a new array with elements that pass a test function.
● reduce(): Reduces the array to a single value by executing a reducer function on
each element.

For Example:

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

// map: doubles each element


const doubled = numbers.map(n => n * 2);
console.log(doubled); // Output: [2, 4, 6, 8, 10]

// filter: keeps even numbers


const evens = numbers.filter(n => n % 2 === 0);
console.log(evens); // Output: [2, 4]

// reduce: calculates the sum


const sum = numbers.reduce((acc, n) => acc + n, 0);
console.log(sum); // Output: 15

33. Explain the concept of prototypes and prototype chain in JavaScript.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
27

Answer:
In JavaScript, every object has a prototype, which is another object it inherits properties from.
When accessing a property, JavaScript first looks at the object itself; if the property is not
found, it checks the prototype. This series of lookups is called the prototype chain.

For Example:

const person = {
greet() {
console.log(`Hello, my name is ${this.name}`);
}
};

const alice = Object.create(person);


alice.name = "Alice";
alice.greet(); // Output: Hello, my name is Alice
console.log(alice.__proto__ === person); // Output: true

Here, alice inherits the greet method from person through the prototype chain.

34. What is the purpose of Object.freeze() and Object.seal()?

Answer:

● Object.freeze(): Prevents any modification to an object’s properties (neither


adding, removing, nor modifying properties is allowed).
● Object.seal(): Prevents adding or removing properties, but allows modification of
existing properties.

For Example:

const obj = { a: 1, b: 2 };

// Object.freeze
Object.freeze(obj);
obj.a = 10; // This will be ignored

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
28

console.log(obj.a); // Output: 1

// Object.seal
const sealedObj = { x: 1, y: 2 };
Object.seal(sealedObj);
sealedObj.x = 10; // Allowed
delete sealedObj.y; // Ignored
console.log(sealedObj); // Output: { x: 10, y: 2 }

35. What are generators, and how do they work?

Answer:
Generators are special functions in JavaScript that can be paused and resumed, allowing
them to produce a series of values over time. They are defined using the function* syntax
and use yield to return values.

For Example:

function* generator() {
yield 1;
yield 2;
yield 3;
}

const gen = generator();


console.log(gen.next().value); // Output: 1
console.log(gen.next().value); // Output: 2
console.log(gen.next().value); // Output: 3
console.log(gen.next().done); // Output: true

36. What is the difference between == and ===?

Answer:

● == (loose equality): Compares values after type coercion.


● === (strict equality): Compares values without type coercion.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
29

For Example:

console.log(5 == '5'); // Output: true (type coercion)


console.log(5 === '5'); // Output: false (different types)
console.log(null == undefined); // Output: true
console.log(null === undefined); // Output: false

37. What is the purpose of the Symbol data type in JavaScript?

Answer:
Symbol is a primitive data type introduced in ES6. It creates unique and immutable values,
which are often used as object property keys to prevent name collisions.

For Example:

const sym1 = Symbol('id');


const sym2 = Symbol('id');
console.log(sym1 === sym2); // Output: false

const obj = {
[sym1]: 'Value associated with sym1'
};
console.log(obj[sym1]); // Output: Value associated with sym1

38. What is the typeof operator, and what are its quirks?

Answer:
The typeof operator returns the data type of a value. However, it has some quirks, especially
with null.

For Example:

console.log(typeof 42); // Output: "number"


console.log(typeof 'Hello'); // Output: "string"

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
30

console.log(typeof null); // Output: "object" (quirk in JavaScript)


console.log(typeof undefined); // Output: "undefined"
console.log(typeof []); // Output: "object"

The quirk with null returning "object" is due to legacy reasons.

39. What is the difference between event delegation and event bubbling?

Answer:

● Event bubbling: An event starts at the target element and propagates up through its
ancestors (from child to parent).
● Event delegation: A technique where a parent element handles events for its children
by leveraging event bubbling. It improves performance by attaching fewer event
listeners.

For Example:

document.getElementById('parent').addEventListener('click', (event) => {


if (event.target.tagName === 'BUTTON') {
console.log('Button clicked:', event.target.textContent);
}
});
// HTML: <div id="parent"><button>Click me</button></div>

40. What are WeakMap and WeakSet in JavaScript?

Answer:

● WeakMap: A collection of key-value pairs where keys must be objects. It holds "weak"
references to objects, allowing garbage collection if there are no other references.
● WeakSet: A collection of unique objects, also holding "weak" references.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
31

let obj = { name: 'Alice' };


const weakMap = new WeakMap();
weakMap.set(obj, 'Developer');
console.log(weakMap.get(obj)); // Output: Developer

const weakSet = new WeakSet();


weakSet.add(obj);
console.log(weakSet.has(obj)); // Output: true

obj = null; // Now, obj can be garbage collected since WeakMap/WeakSet do not
prevent it.

SCENARIO QUESTIONS
41. Scenario: You need to store the configuration settings for an application
that won't change during runtime.

Question: Which variable declaration should you use: var, let, or const? Explain the
behavior with an example.

Answer:
You should use const for variables whose values should not change during runtime. This
ensures the variable is read-only and prevents reassignment. Using let or var for such cases
is not recommended since they allow reassignment, which can lead to bugs. However, note
that objects or arrays declared with const can have their contents modified, though the
reference itself cannot change.

For Example:

const CONFIG = {
apiUrl: 'https://api.example.com',
timeout: 5000
};

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
32

// CONFIG = {}; // Error: Assignment to constant variable

CONFIG.timeout = 10000; // Modifying an object property is allowed


console.log(CONFIG);

Resulting Table:

Variable Type Scope Can Reassign? Hoisted? Use Case

var Function/global Yes Yes Avoid; legacy usage

let Block Yes No Use when value changes

const Block No No Use for constant references

42. Scenario: You want to create a counter that increments every time a
button is clicked and display the count.

Question: How would you implement a counter using closures to preserve the state between
button clicks?

Answer:
Using closures, we can create a function that retains access to its lexical scope, even after the
outer function has finished execution. This ensures that the state of the count variable
persists between button clicks.

For Example:

function createCounter() {
let count = 0; // Private variable inside the closure
return function () {
count++;
console.log(`Button clicked ${count} times.`);
};
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
33

const incrementCounter = createCounter();


document.getElementById('button').addEventListener('click', incrementCounter);

Here, count is a private variable maintained by the closure returned from createCounter.
Each time the button is clicked, the incrementCounter function increments and logs the
count value.

43. Scenario: You are building a search input field that suggests results.
However, to avoid sending too many API requests, you need to optimize it.

Question: How would you use debounce to limit API calls during user input?

Answer:
Debouncing ensures that a function is executed only after the user has stopped triggering
the event for a specified time, reducing unnecessary operations. This is especially useful in
search inputs, where every keystroke can otherwise lead to API requests.

For Example:

function debounce(func, delay) {


let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), delay);
};
}

const fetchSuggestions = debounce((query) => {


console.log(`Fetching suggestions for: ${query}`);
}, 500);

document.getElementById('searchInput').addEventListener('input', (event) => {


fetchSuggestions(event.target.value);
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
34

In this example, the function only triggers 500ms after the user stops typing, reducing the
number of API calls.

44. Scenario: You need to filter a list of products based on user-selected


categories.

Question: Which loop would you use to iterate over the selected filters: for...in or
for...of?

Answer:
Use for...of when iterating over arrays, as it allows easy access to each element. for...in
is more suitable for objects where you need to access keys. In this scenario, since the filters
are an array, for...of is the better choice.

For Example:

const selectedFilters = ['Electronics', 'Furniture', 'Clothing'];

for (const filter of selectedFilters) {


console.log(`Filter applied: ${filter}`);
}

Resulting Table:

Loop Type Use Case Access Example Use

for...in Iterate over object keys Keys Iterating over object props

for...of Iterate over array elements Values Loop through an array

45. Scenario: You need to display a user-specific dashboard based on their


role (Admin/User).

Question: How would you implement this using control structures?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
35

Answer:
In this scenario, if-else statements can be used to check the role of the user and display the
corresponding dashboard. This provides clear decision-making based on the value of
userRole.

For Example:

const userRole = 'Admin';

if (userRole === 'Admin') {


console.log('Loading Admin Dashboard...');
} else if (userRole === 'User') {
console.log('Loading User Dashboard...');
} else {
console.log('Access Denied.');
}

Here, the if-else structure ensures that the correct message is displayed based on the
user’s role.

46. Scenario: You want to initialize certain values at the start of your
program.

Question: How would you use an IIFE for initialization?

Answer:
An Immediately Invoked Function Expression (IIFE) runs immediately when defined,
making it useful for setting up initial values or configurations.

For Example:

(function () {
const initialValue = 42;
console.log(`Initialized with value: ${initialValue}`);
})();

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
36

This ensures that initialValue is only available within the IIFE, preventing it from polluting
the global scope.

47. Scenario: You need to perform arithmetic operations on two numbers.

Question: How would you use arithmetic operators in a function?

Answer:
You can create a function that uses arithmetic operators like addition, subtraction,
multiplication, and division to perform operations on two numbers.

For Example:

function arithmeticOperations(a, b) {
console.log(`Addition: ${a + b}`);
console.log(`Subtraction: ${a - b}`);
console.log(`Multiplication: ${a * b}`);
console.log(`Division: ${a / b}`);
}

arithmeticOperations(10, 5);

Resulting Table:

Operator Description Example Result

+ (Add) Adds two numbers 10 + 5 15

- (Sub) Subtracts two numbers 10 - 5 5

* (Mul) Multiplies two numbers 10 * 5 50

/ (Div) Divides two numbers 10 / 5 2

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
37

48. Scenario: You need to calculate the sum of numbers from 1 to 10 using a
loop.

Question: Which loop would you use to calculate the sum?

Answer:
You can use a for loop to iterate through numbers from 1 to 10 and accumulate the sum.

For Example:

let sum = 0;
for (let i = 1; i <= 10; i++) {
sum += i;
}
console.log(`Sum of numbers from 1 to 10: ${sum}`);

This example demonstrates how the loop adds each number to sum until it reaches 10.

49. Scenario: You need to manage multiple user states like "active",
"inactive", and "banned".

Question: How would you implement this using a switch statement?

Answer:
A switch statement is effective for handling multiple specific cases.

For Example:

const userState = 'active';

switch (userState) {
case 'active':
console.log('User is active.');
break;
case 'inactive':

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
38

console.log('User is inactive.');
break;
case 'banned':
console.log('User is banned.');
break;
default:
console.log('Unknown state.');
}

The switch ensures each state is handled individually.

50. Scenario: You want to create a function that takes another function as
an argument.

Question: How would you implement a higher-order function?

Answer:
A higher-order function is a function that takes another function as an argument or returns
a function.

For Example:

function greetUser(name) {
console.log(`Hello, ${name}!`);
}

function executeFunction(func, arg) {


func(arg);
}

executeFunction(greetUser, 'Alice');

This example shows how executeFunction takes greetUser as an argument and executes it.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
39

51. Scenario: You need to create a variable that may change within a loop
but should not affect the global scope.

Question: Should you use var or let inside a loop, and why?

Answer:
Use let inside a loop because it ensures the variable is scoped only to that loop block,
preventing it from affecting variables outside the block. Using var would cause the variable
to leak into the function or global scope, potentially causing bugs if reused unintentionally
across iterations.

For Example:

for (let i = 0; i < 3; i++) {


console.log(i); // Output: 0, 1, 2
}
// console.log(i); // Error: i is not defined

If var were used instead of let, the i variable would be accessible outside the loop, possibly
causing conflicts.

Resulting Table:

Keyword Scope Reassignment Allowed Block Scoped Hoisted

var Function/Global Yes No Yes

let Block Yes Yes No

const Block No Yes No

52. Scenario: You want to store a large integer value that exceeds the safe
range of JavaScript numbers.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
40

Question: How would you use the BigInt data type to store this value?

Answer:
JavaScript’s regular numbers can accurately represent integers only up to 2^53 - 1. To handle
larger integers, you can use BigInt, which allows you to safely store and manipulate
numbers beyond this limit.

For Example:

const maxSafeInteger = Number.MAX_SAFE_INTEGER;


console.log(maxSafeInteger); // Output: 9007199254740991

const largeNumber = BigInt(maxSafeInteger) + BigInt(1);


console.log(largeNumber); // Output: 9007199254740992n

Resulting Table:

Data Type Range Use Case

number Up to 2^53 - 1 (9007199254740991) For small or standard integers

BigInt No upper limit For large integers beyond safe range

53. Scenario: You need to find out if a given value is null or undefined.

Question: How would you distinguish between null and undefined in JavaScript?

Answer:
In JavaScript, null is an assigned value representing the absence of any object, while
undefined indicates that a variable has been declared but not initialized. They are distinct,
though both imply the absence of value.

For Example:

let a;

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
41

let b = null;

console.log(a === undefined); // Output: true


console.log(b === null); // Output: true

Resulting Table:

Value Description Example

undefined Variable declared but not assigned a value let x;

null Explicitly assigned to represent "no value" let x = null;

54. Scenario: You want to compare two values but need to avoid
unexpected type coercion.

Question: Should you use == or === for comparisons, and why?

Answer:
Always use === for comparisons to avoid type coercion. The === operator ensures both the
value and the type are the same, reducing the risk of unexpected behavior.

For Example:

console.log(5 == '5'); // Output: true (type coercion)


console.log(5 === '5'); // Output: false (different types)

Resulting Table:

Operator Description Example Output

== Loose equality (with type coercion) 5 == '5' true

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
42

=== Strict equality (without type coercion) 5 === '5' false

55. Scenario: You need to loop through the properties of an object to


display its key-value pairs.

Question: Which loop should you use, and how?

Answer:
The for...in loop is ideal for iterating over the properties (keys) of an object. It allows you to
access both the key and the corresponding value.

For Example:

const user = { name: 'Alice', age: 25 };

for (const key in user) {


console.log(`${key}: ${user[key]}`);
}
// Output:
// name: Alice
// age: 25

Resulting Table:

Loop Use Case Access Example

for...in Iterates over object properties Keys & Values Loop through object keys

for...of Iterates over array elements Values Loop through arrays

56. Scenario: You need to create a function that doesn’t bind its own this
context.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
43

Question: How would you use an arrow function to achieve this?

Answer:
Arrow functions inherit the this context from their parent scope. This makes them useful
when you want to maintain the same this context, avoiding unintended behavior inside
callbacks.

For Example:

const person = {
name: 'Alice',
greet: () => console.log(`Hello, ${this.name || 'Guest'}`),
};

person.greet(); // Output: Hello, Guest

Here, this.name does not refer to the person object because arrow functions do not bind
their own this.

57. Scenario: You need to make a decision based on multiple conditions.

Question: How would you structure a conditional using an if-else statement?

Answer:
An if-else statement is useful when you need to handle multiple conditions and execute
different blocks of code based on the results.

For Example:

const age = 20;

if (age < 18) {


console.log('You are a minor.');
} else if (age >= 18 && age < 65) {
console.log('You are an adult.');
} else {
console.log('You are a senior citizen.');

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
44

}
// Output: You are an adult.

58. Scenario: You need to execute code multiple times until a condition is
met.

Question: How would you use a while loop to achieve this?

Answer:
Use a while loop when the number of iterations is unknown, and you want to keep looping
until a condition is satisfied.

For Example:

let count = 0;
while (count < 3) {
console.log(`Count: ${count}`);
count++;
}
// Output:
// Count: 0
// Count: 1
// Count: 2

59. Scenario: You need to break out of a loop when a specific condition is
met.

Question: How would you use the break statement in a loop?

Answer:
The break statement exits the loop immediately when the specified condition is met.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
45

for (let i = 0; i < 5; i++) {


if (i === 3) break;
console.log(i);
}
// Output: 0, 1, 2

60. Scenario: You want to skip certain iterations in a loop based on a


condition.

Question: How would you use the continue statement in a loop?

Answer:
The continue statement skips the current iteration and moves to the next one. This is useful
when you want to bypass specific cases without breaking the entire loop.

For Example:

for (let i = 0; i < 5; i++) {


if (i === 2) continue;
console.log(i);
}
// Output: 0, 1, 3, 4

Resulting Table:

Statement Description Example Effect


Condition

break Exits the loop immediately i === 3 Stops the loop

continue Skips the current iteration and i === 2 Skips iteration for
proceeds to the next i = 2

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
46

61. Scenario: You need to manage multiple asynchronous tasks and


execute code after all of them are completed.

Question: How would you use Promise.all() to handle multiple asynchronous operations?

Answer:
Promise.all() runs multiple promises in parallel and returns a new promise that resolves
when all input promises are fulfilled or rejects if any one promise fails. This method is useful
when you need to wait for several asynchronous operations to complete before proceeding.

For Example:

const promise1 = new Promise((resolve) => setTimeout(resolve, 1000, 'Task 1


Complete'));
const promise2 = new Promise((resolve) => setTimeout(resolve, 500, 'Task 2
Complete'));
const promise3 = new Promise((resolve) => setTimeout(resolve, 1500, 'Task 3
Complete'));

Promise.all([promise1, promise2, promise3])


.then((results) => console.log('All tasks completed:', results))
.catch((error) => console.error('A task failed:', error));

// Output after 1.5 seconds:


// All tasks completed: [ 'Task 1 Complete', 'Task 2 Complete', 'Task 3 Complete' ]

Resulting Table:

Promise Method Description Example Usage

Promise.all() Waits for all promises to resolve or any Multiple API calls in
to reject parallel

Promise.race() Resolves/rejects as soon as the first Timeout race between


promise settles requests

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
47

Promise.any() Resolves as soon as any promise Get the first successful


resolves result

Promise.allSettled() Waits for all promises to settle Useful for collecting all
(resolve/reject) results

62. Scenario: You need to ensure that an asynchronous operation executes


even if an error occurs.

Question: How would you use Promise.finally() in JavaScript?

Answer:
finally() is a method on promises that executes code after the promise has settled,
regardless of whether it was fulfilled or rejected. This is useful for cleaning up resources or
logging completion of an operation.

For Example:

fetch('https://jsonplaceholder.typicode.com/posts/1')
.then((response) => response.json())
.then((data) => console.log('Data:', data))
.catch((error) => console.error('Error:', error))
.finally(() => console.log('Operation Complete'));

// Output:
// Data: { ...post data... }
// Operation Complete

Resulting Table:

Promise Use Case Executes After Executes After


Method Resolution Rejection

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
48

then() Handle successful Yes No


operation

catch() Handle errors No Yes

finally() Cleanup tasks Yes Yes

63. Scenario: You need to control the order of asynchronous operations to


ensure one runs only after another completes.

Question: How would you use async/await to achieve this?

Answer:
With async/await, you can write asynchronous code that looks synchronous, ensuring one
operation completes before the next starts. This approach avoids deeply nested .then()
callbacks and improves code readability.

For Example:

async function fetchData() {


try {
const response1 = await fetch('https://jsonplaceholder.typicode.com/posts/1');
const post = await response1.json();
console.log('Post:', post);

const response2 = await fetch('https://jsonplaceholder.typicode.com/users/1');


const user = await response2.json();
console.log('User:', user);
} catch (error) {
console.error('Error:', error);
}
}

fetchData();

Resulting Table:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
49

Feature Description Example Usage

async Declares a function as asynchronous async function myFunc() {}

await Pauses execution until promise resolves const data = await fetch(url)

64. Scenario: You want to avoid "callback hell" in asynchronous


programming.

Question: How does async/await improve the readability of asynchronous code compared
to callbacks?

Answer:
Using async/await makes asynchronous code look like synchronous code, improving
readability. It also helps avoid "callback hell," where callbacks are deeply nested, making code
difficult to read and maintain.

For Example:

// Without async/await: Callback hell


fetch('https://jsonplaceholder.typicode.com/posts/1')
.then((response) => response.json())
.then((post) => {
console.log(post);
return fetch('https://jsonplaceholder.typicode.com/users/1');
})
.then((response) => response.json())
.then((user) => console.log(user))
.catch((error) => console.error('Error:', error));

// With async/await: Cleaner code


async function fetchData() {
try {
const post = await
fetch('https://jsonplaceholder.typicode.com/posts/1').then((res) => res.json());
console.log(post);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
50

const user = await


fetch('https://jsonplaceholder.typicode.com/users/1').then((res) => res.json());
console.log(user);
} catch (error) {
console.error('Error:', error);
}
}

fetchData();

65. Scenario: You need to create an event listener for multiple buttons
using event delegation.

Question: How would you implement event delegation using JavaScript?

Answer:
Event delegation leverages event bubbling, where events on child elements propagate to
their parent. You can attach a single event listener on a parent element and handle events
for multiple child elements efficiently.

For Example:

document.getElementById('parent').addEventListener('click', (event) => {


if (event.target.tagName === 'BUTTON') {
console.log(`Button clicked: ${event.target.textContent}`);
}
});

// HTML:
// <div id="parent">
// <button>Button 1</button>
// <button>Button 2</button>
// </div>

// Output (on click): Button clicked: Button 1 (or Button 2)

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
51

66. Scenario: You need to create private variables in JavaScript to avoid


polluting the global scope.

Question: How would you use closures to create private variables?

Answer:
Closures allow you to create private variables by keeping them within an inner function's
scope, which is not accessible from the outside.

For Example:

function createCounter() {
let count = 0; // Private variable
return function () {
count++;
console.log(`Count: ${count}`);
};
}

const counter = createCounter();


counter(); // Output: Count: 1
counter(); // Output: Count: 2

Resulting Table:

Concept Description Example Usage

Closure Inner function retains access to outer function Counter, private


variables variables

67. Scenario: You need to ensure only one instance of an object exists
across the application.

Question: How would you implement the singleton pattern in JavaScript?

Answer:
A singleton pattern ensures that only one instance of an object is created and provides a
global point of access to it.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
52

For Example:

const Singleton = (function () {


let instance;

function createInstance() {
return { name: 'Singleton Instance' };
}

return {
getInstance: function () {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();

const instance1 = Singleton.getInstance();


const instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // Output: true

68. Scenario: You want to compare objects but JavaScript compares them
by reference, not value.

Question: How would you compare two objects by value in JavaScript?

Answer:
Since objects are compared by reference, use JSON.stringify() to compare their values.

For Example:

function areObjectsEqual(obj1, obj2) {


return JSON.stringify(obj1) === JSON.stringify(obj2);
}

const obj1 = { a: 1, b: 2 };

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
53

const obj2 = { a: 1, b: 2 };

console.log(areObjectsEqual(obj1, obj2)); // Output: true

69. Scenario: You need to delay the execution of code by a few seconds.

Question: How would you implement a delay using setTimeout?

Answer:
setTimeout executes a function after a specified delay.

For Example:

console.log('Start');
setTimeout(() => console.log('Executed after 2 seconds'), 2000);
console.log('End');

// Output:
// Start
// End
// Executed after 2 seconds

70. Scenario: You need to run a task periodically, such as updating the time
on a webpage.

Question: How would you use setInterval to execute a task at regular intervals?

Answer:
setInterval repeatedly executes a function at the specified interval.

For Example:

setInterval(() => {
const now = new Date();
console.log(`Current Time: ${now.toLocaleTimeString()}`);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
54

}, 1000);

// Output (every second):


// Current Time: 10:20:30 AM
// Current Time: 10:20:31 AM

Use clearInterval() if you need to stop the interval.

71. Scenario: You need to throttle user interactions to improve


performance, such as limiting the number of clicks a button can register.

Question: How would you implement throttling in JavaScript?

Answer:
Throttling ensures that a function is called at most once every specified interval, even if it is
triggered multiple times during that interval. This technique is useful for events that fire
continuously, such as scrolling or button clicks, where running the function too frequently
can degrade performance.

For Example:

function throttle(func, delay) {


let lastCall = 0;
return function (...args) {
const now = new Date().getTime();
if (now - lastCall >= delay) {
lastCall = now;
func.apply(this, args);
}
};
}

const handleClick = throttle(() => {


console.log('Button clicked!');
}, 2000);

document.getElementById('button').addEventListener('click', handleClick);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
55

In this example, the handleClick function will execute at most once every 2 seconds, no
matter how frequently the button is clicked.

Resulting Table:

Concept Description Example Usage

Throttling Executes function at controlled intervals Scroll events, button clicks

Debouncing Delays function execution until the event Search input, form
stops validation

72. Scenario: You need to memoize the results of an expensive function to


avoid redundant calculations.

Question: How would you implement memoization in JavaScript?

Answer:
Memoization is a technique that stores the results of expensive function calls to reuse them
when the same inputs occur again, improving performance.

For Example:

function memoize(fn) {
const cache = {};
return function (...args) {
const key = JSON.stringify(args);
if (cache[key]) {
console.log('Fetching from cache:', key);
return cache[key];
}
console.log('Calculating result for:', key);
const result = fn(...args);
cache[key] = result;
return result;
};

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
56

const add = memoize((a, b) => a + b);

console.log(add(1, 2)); // Calculating result: 3


console.log(add(1, 2)); // Fetching from cache: 3

In this example, the second call to add(1, 2) fetches the result from the cache, avoiding
unnecessary computation.

Resulting Table:

Concept Description Use Case

Memoization Caches function results for reuse Expensive calculations

Caching Stores data for faster future retrieval API response caching

73. Scenario: You want to implement a custom iterable object.

Question: How would you use the iterator protocol in JavaScript?

Answer:
The iterator protocol allows objects to be iterable by defining a Symbol.iterator method
that returns an iterator. This iterator provides a next() method that returns objects
containing value and done properties.

For Example:

const myIterable = {
data: [1, 2, 3, 4],
[Symbol.iterator]() {
let index = 0;
const data = this.data;
return {
next() {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
57

return index < data.length


? { value: data[index++], done: false }
: { done: true };
},
};
},
};

for (const value of myIterable) {


console.log(value); // Output: 1, 2, 3, 4
}

This example shows how to make the custom object myIterable behave like an iterable,
allowing it to work with for...of loops.

Resulting Table:

Concept Description Use Case

Iterator Protocol Defines iteration over an object Custom iterable objects

Generator Produces multiple values on demand Infinite sequences, lazy loading

74. Scenario: You need to encapsulate module functionality to prevent


access from outside.

Question: How would you implement a JavaScript module using IIFE?

Answer:
An IIFE (Immediately Invoked Function Expression) allows you to create a private scope and
expose only selected parts of the code, simulating a module.

For Example:

const myModule = (function () {


let privateVar = 0;

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
58

function privateMethod() {
console.log('Private method called');
}

return {
increment: function () {
privateVar++;
console.log(`Current value: ${privateVar}`);
},
};
})();

myModule.increment(); // Output: Current value: 1


// myModule.privateMethod(); // Error: Not accessible

In this example, only the increment method is accessible outside the module, while
privateMethod and privateVar remain private.

75. Scenario: You need to flatten a deeply nested array.

Question: How would you use recursion to flatten a nested array?

Answer:
A recursive function can flatten arrays with multiple levels of nesting.

For Example:

function flattenArray(arr) {
return arr.reduce((acc, value) =>
Array.isArray(value) ? acc.concat(flattenArray(value)) : acc.concat(value),
[]);
}

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


console.log(flattenArray(nestedArray)); // Output: [1, 2, 3, 4, 5]

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
59

This recursive approach ensures that all nested arrays are flattened into a single-level array.

76. Scenario: You need to handle circular references when converting an


object to JSON.

Question: How would you safely serialize an object with circular references?

Answer:
Circular references occur when an object references itself. A custom serializer can detect
such references to avoid errors.

For Example:

const obj = {};


obj.self = obj;

function safeStringify(obj) {
const seen = new WeakSet();
return JSON.stringify(obj, (key, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) return '[Circular]';
seen.add(value);
}
return value;
});
}

console.log(safeStringify(obj)); // Output: {"self":"[Circular]"}

77. Scenario: You need to deep-clone an object containing nested


structures.

Question: How would you perform a deep clone in JavaScript?

Answer:
You can use structuredClone() or JSON.parse(JSON.stringify()) to deep-clone an

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
60

object. However, structuredClone() is preferred for handling special cases like circular
references and Date objects.

For Example:

const obj = { a: 1, b: { c: 2 } };
const deepClone = JSON.parse(JSON.stringify(obj));

deepClone.b.c = 42;
console.log(obj.b.c); // Output: 2

78. Scenario: You need to execute asynchronous code sequentially without


using async/await.

Question: How would you chain promises to ensure sequential execution?

Answer:
You can chain promises to execute asynchronous tasks in sequence.

For Example:

fetch('https://jsonplaceholder.typicode.com/posts/1')
.then((response) => response.json())
.then((post) => {
console.log(post);
return fetch('https://jsonplaceholder.typicode.com/users/1');
})
.then((response) => response.json())
.then((user) => console.log(user))
.catch((error) => console.error('Error:', error));

This example ensures that the second fetch request runs only after the first completes.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
61

79. Scenario: You need to lazy-load images on a webpage to improve


performance.

Question: How would you implement lazy-loading in JavaScript?

Answer:
Use the Intersection Observer API to detect when images enter the viewport and load them
dynamically.

For Example:

const images = document.querySelectorAll('img[data-src]');

const observer = new IntersectionObserver((entries) => {


entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
});

images.forEach((img) => observer.observe(img));

80. Scenario: You need to debounce a function to reduce the frequency of


API calls during user input.

Question: How would you implement debouncing in JavaScript?

Answer:
Debouncing delays the function execution until the user stops triggering the event,
reducing unnecessary calls.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
62

function debounce(func, delay) {


let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), delay);
};
}

const handleInput = debounce((input) => {


console.log(`Fetching results for: ${input}`);
}, 500);

document.getElementById('searchInput').addEventListener('input', (event) => {


handleInput(event.target.value);
});

Resulting Table:

Concept Description Use Case

Debouncing Executes after events stop triggering Search input, API requests

Throttling Executes at regular intervals Button clicks, scroll events

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
63

Chapter 2: DOM Manipulation & Browser APIs


THEORETICAL QUESTIONS

1. What is the DOM in JavaScript?

Answer:
The DOM (Document Object Model) is an API that allows JavaScript to interact with the
structure of a webpage. When a browser loads a web page, it parses the HTML and creates a
tree-like representation called the DOM. This tree consists of nodes: elements, attributes, and
text. Using the DOM, JavaScript can manipulate the structure and style of a webpage
dynamically, without needing to reload it. This manipulation includes tasks like adding,
removing, or modifying elements or text.

For Example:

// Selecting an element by ID and changing its content


const heading = document.getElementById('title');
heading.innerHTML = 'Welcome to JavaScript DOM Manipulation';

In this example, the getElementById method selects the element with the ID title. The
innerHTML property updates the content of this element, reflecting the change on the page.

2. How do you select elements in the DOM using JavaScript?

Answer:
JavaScript offers several ways to select elements from the DOM. Selection methods depend
on the type of element you're targeting. Some commonly used methods are:

● getElementById: Returns a single element by ID.


● querySelector: Selects the first element that matches a CSS selector.
● querySelectorAll: Returns a list of all matching elements.
● getElementsByClassName: Retrieves elements with the specified class name.
● getElementsByTagName: Selects all elements with the given tag name.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
64

// Selecting multiple elements with querySelectorAll


const items = document.querySelectorAll('.list-item');
items.forEach(item => item.style.color = 'blue');

Here, the code selects all elements with the class list-item and sets their text color to blue
using the style property. This is particularly useful when applying changes to multiple
elements at once.

3. What is innerHTML and how is it used?

Answer:
The innerHTML property provides a way to read or change the content inside an element,
including HTML markup. It is used to inject text, images, or entire HTML elements
dynamically.

For Example:

// Changing the inner HTML content of a div


const div = document.querySelector('#content');
div.innerHTML = '<p>This is a new paragraph.</p>';

In this example, the innerHTML property replaces the current content of the div with a new
paragraph. However, be cautious when using innerHTML because it can expose your site to
Cross-Site Scripting (XSS) attacks if untrusted user input is injected.

4. What is classList and how is it used?

Answer:
The classList property allows you to manipulate the CSS classes of an element efficiently.
You can add, remove, toggle, or check if a specific class is present without overwriting
existing classes.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
65

// Toggling a class on a button click


const button = document.querySelector('#toggleButton');
button.addEventListener('click', () => {
button.classList.toggle('active');
});

In this example, each click on the button toggles the active class. If the class is present, it
gets removed; if it’s not, it gets added.

5. How do you add event listeners to elements?

Answer:
The addEventListener method allows you to attach an event handler to an element. It can
listen for various events like click, keyup, mouseover, etc. Unlike the older onclick attribute,
addEventListener supports multiple handlers for the same event.

For Example:

// Adding a click event listener to a button


const button = document.querySelector('#clickMe');
button.addEventListener('click', () => {
alert('Button clicked!');
});

In this example, clicking the button triggers an alert. Using addEventListener keeps your
JavaScript code modular and helps separate behavior from structure.

6. What is Event Delegation and how does it work?

Answer:
Event Delegation leverages event bubbling, where events propagate from child to parent
elements. Instead of attaching listeners to each child, you can place a single listener on a

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
66

parent to manage events on all children. This is especially useful for dynamically generated
elements.

For Example:

// Using event delegation on a parent element


document.querySelector('#parentList').addEventListener('click', (event) => {
if (event.target.tagName === 'LI') {
alert(`You clicked on ${event.target.textContent}`);
}
});

In this example, the parent <ul> element listens for clicks on any of its <li> children, even if
they are added later.

7. What is the BOM (Browser Object Model)?

Answer:
The Browser Object Model (BOM) allows interaction with the browser itself. It provides
objects like window, navigator, and history to control the browser environment. Using
BOM, you can perform tasks like opening new tabs, navigating between pages, or storing
data in localStorage and sessionStorage.

For Example:

// Accessing the window object


console.log(window.location.href); // Logs the current URL

Here, the window object provides information about the browser window, including the
current page URL using location.href.

8. What is the difference between localStorage and sessionStorage?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
67

Answer:
Both localStorage and sessionStorage store data in the browser, but they differ in how
long the data persists:

● localStorage: Data remains even if the browser or tab is closed.


● sessionStorage: Data is cleared when the tab or browser session ends.

For Example:

// Storing and retrieving data from localStorage


localStorage.setItem('username', 'Shardul');
console.log(localStorage.getItem('username')); // Output: Shardul

In this example, the localStorage retains data across browser sessions. Use localStorage
for longer-term data and sessionStorage for temporary data.

9. How do you make AJAX calls using the Fetch API?

Answer:
The Fetch API simplifies making asynchronous HTTP requests. It returns a Promise, which
allows for easier handling of success and error responses compared to older AJAX methods.

For Example:

// Fetching data from an API


fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

In this example, the fetch method requests data from an API. If successful, the response is
converted to JSON and logged.

10. What are setTimeout and setInterval in JavaScript?


INTERVIEWNINJA.IN ECOMNOWVENTURESTM
68

Answer:
The setTimeout function executes a task after a specified delay, while setInterval repeats a
task at specified intervals. Both functions allow JavaScript to manage time-based events.

For Example:

// Using setTimeout to delay a message


setTimeout(() => {
console.log('This message is delayed by 2 seconds');
}, 2000);

// Using setInterval to print a message every second


const intervalId = setInterval(() => {
console.log('This message prints every second');
}, 1000);

// Stop the interval after 5 seconds


setTimeout(() => clearInterval(intervalId), 5000);

In this example, setTimeout delays the message by 2 seconds, while setInterval repeatedly
prints a message every second. The interval is cleared after 5 seconds using clearInterval.

11. What is Event Bubbling in JavaScript?

Answer:
Event Bubbling is a process in which an event starts from the target element (where it is
triggered) and propagates up through its parent elements in the DOM tree until it reaches
the root element (like <html> or document). This happens by default for most events, such as
click and keyup. It enables parent elements to react to events triggered by their child
elements. However, in cases where this behavior is not desired, the event propagation can be
stopped using event.stopPropagation().

For Example:

// Example of event bubbling

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
69

const parent = document.querySelector('#parentDiv');


const child = document.querySelector('#childDiv');

parent.addEventListener('click', () => console.log('Parent clicked'));


child.addEventListener('click', () => console.log('Child clicked'));

// Output when clicking the child:


// Child clicked
// Parent clicked

Here, the click event on the child <div> triggers both the child and the parent’s click event
handlers because the event bubbles up from the child to the parent.

12. What is Event Capturing in JavaScript?

Answer:
In Event Capturing, the event starts from the topmost parent element and travels down
through the DOM tree until it reaches the target element. By default, JavaScript uses event
bubbling, but you can enable capturing by setting the third parameter of
addEventListener() to true. Event capturing is less commonly used, but it can be helpful
when you want a parent element to handle events before they reach the target element.

For Example:

// Example of event capturing


const parent = document.querySelector('#parentDiv');
const child = document.querySelector('#childDiv');

parent.addEventListener('click', () => console.log('Parent clicked'), true);


child.addEventListener('click', () => console.log('Child clicked'), true);

// Output when clicking the child:


// Parent clicked
// Child clicked

In this example, the click event triggers the parent’s event handler first because capturing is
enabled.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
70

13. How can you prevent an event from propagating?

Answer:
To stop an event from propagating through the DOM tree (either during bubbling or
capturing), you can use event.stopPropagation(). This method is helpful when you want to
restrict an event to the target element only, without letting it affect parent elements.

For Example:

// Preventing event propagation


const parent = document.querySelector('#parentDiv');
const child = document.querySelector('#childDiv');

parent.addEventListener('click', () => console.log('Parent clicked'));


child.addEventListener('click', (event) => {
event.stopPropagation();
console.log('Child clicked');
});

In this example, clicking the child element logs "Child clicked," but the parent’s event listener
is not triggered due to the use of stopPropagation().

14. How do you prevent the default action of an event?

Answer:
In JavaScript, certain events come with a default action—for example, submitting a form on
button click or navigating to another page when a link is clicked. To prevent this behavior,
you can use event.preventDefault(). This method is commonly used to control form
submissions or navigation links.

For Example:

// Preventing the default form submission


const form = document.querySelector('#myForm');
form.addEventListener('submit', (event) => {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
71

event.preventDefault();
console.log('Form submission prevented');
});

In this example, the default form submission is stopped, and the message "Form submission
prevented" is logged instead.

15. What is the difference between window and document objects?

Answer:
The window object represents the entire browser window, serving as the global context in a
web page. It includes properties and methods to control the browser window, such as
alert(), location, and innerWidth.

The document object, on the other hand, represents the content of the web page currently
loaded in the browser. It allows you to manipulate HTML elements within the page.

For Example:

console.log(window.innerWidth); // Logs the width of the browser window


console.log(document.title); // Logs the title of the web page

Here, the window object provides the width of the browser window, and the document object
gives the title of the current web page.

16. How do localStorage and cookies differ?

Answer:
Both localStorage and cookies allow data storage on the client side, but they have different
characteristics:

● localStorage: Stores larger data (5-10 MB) that persists until manually cleared by the
user or code. It is accessible only from JavaScript.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
72

● Cookies: Limited to 4 KB in size, can be accessed by both client and server, and often
have expiration dates for tracking sessions.

For Example:

// Setting and getting a cookie


document.cookie = "user=Shardul; expires=Fri, 31 Dec 2024 12:00:00 UTC;";
console.log(document.cookie); // Output: user=Shardul

Cookies are useful for session management, while localStorage is better for larger and
long-term data.

17. What is the purpose of navigator in the BOM?

Answer:
The navigator object provides information about the browser, device, and operating
environment. It helps developers detect the browser version, user’s preferred language, and
whether cookies are enabled. This can be useful for browser-specific optimizations or
tracking user settings.

For Example:

console.log(navigator.userAgent); // Logs the browser's user agent string


console.log(navigator.language); // Logs the user's preferred language

In this example, the navigator object provides details about the browser type and the
language set by the user.

18. How does history work in the BOM?

Answer:
The history object allows navigation through the user's browser history. It is part of the
BOM and provides methods such as:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
73

● history.back(): Navigates to the previous page.


● history.forward(): Moves forward to the next page.
● history.go(): Moves forward or backward by a specific number of pages.

For Example:

// Navigating through browser history


history.back(); // Goes to the previous page
history.forward(); // Moves forward in the history
history.go(-1); // Same as history.back()

Using the history object, you can programmatically control browser navigation.

19. How do you handle errors in the Fetch API?

Answer:
Since the Fetch API returns a Promise, you need to handle potential errors using .catch()
or try-catch blocks when using async/await. If the request fails, the error can be caught
and logged or handled appropriately.

For Example:

// Handling errors in Fetch API


fetch('https://jsonplaceholder.typicode.com/invalid-url')
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => console.log(data))
.catch(error => console.error('Fetch error:', error));

In this example, if the request fails or the response is not OK, an error is thrown and handled
in the .catch() block.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
74

20. How do you clear a timer set with setInterval?

Answer:
The setInterval() function repeats a given task at regular intervals until it is stopped using
clearInterval(). You need to store the interval ID returned by setInterval() to clear it
later.

For Example:

// Setting and clearing an interval


const intervalId = setInterval(() => {
console.log('This prints every second');
}, 1000);

// Stop the interval after 3 seconds


setTimeout(() => {
clearInterval(intervalId);
console.log('Interval cleared');
}, 3000);

In this example, the interval prints a message every second, but it is stopped after 3 seconds
using clearInterval(). This demonstrates how you can manage repeating tasks efficiently.

21. How does JavaScript handle asynchronous operations using Promises?

Answer:
JavaScript handles asynchronous operations using Promises to prevent blocking the main
thread. A Promise is an object representing an operation that may be completed in the
future or may fail. This avoids "callback hell" by enabling structured chaining through
.then() and .catch(). A pending Promise can either resolve (complete successfully) or
reject (fail). Promises are useful in network requests, timers, or file reading operations.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
75

const fetchData = () => {


return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Data fetched successfully');
}, 1000);
});
};

fetchData()
.then(data => console.log(data)) // Output: Data fetched successfully
.catch(error => console.error(error));

Here, after 1 second, the Promise resolves, logging "Data fetched successfully". If an error
occurred, it would be handled in .catch().

22. What is the difference between async and await?

Answer:
async and await are syntactic sugar for handling Promises, making asynchronous code look
synchronous. Declaring a function as async ensures it returns a Promise. Within an async
function, the await keyword pauses the function until the Promise is resolved, improving
code readability.

For Example:

const fetchUser = async () => {


try {
const response = await fetch('https://jsonplaceholder.typicode.com/users/1');
const user = await response.json();
console.log(user);
} catch (error) {
console.error('Error fetching user:', error);
}
};

fetchUser();

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
76

Here, await waits for the API response and JSON conversion before logging the result.

23. How does JavaScript implement closures?

Answer:
A closure is when a function retains access to variables from its parent scope, even after the
outer function finishes executing. Closures are used to encapsulate data and create private
state.

For Example:

function counter() {
let count = 0;
return function () {
count++;
console.log(`Count: ${count}`);
};
}

const increment = counter();


increment(); // Count: 1
increment(); // Count: 2

Here, the inner function retains access to the count variable, even though the outer
counter() function has already finished executing.

24. How does JavaScript handle event loops and concurrency?

Answer:
JavaScript is single-threaded but uses the event loop to manage concurrency. When an
asynchronous task is initiated (like a timer or API request), it’s handled outside the main
thread. Once completed, the event loop places it back in the call stack to execute.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
77

console.log('Start');
setTimeout(() => console.log('Inside Timeout'), 0);
console.log('End');

// Output:
// Start
// End
// Inside Timeout

Here, the synchronous code runs first. Even though setTimeout has no delay, its callback is
added to the task queue and executed after the synchronous code finishes.

25. How do you implement debouncing in JavaScript?

Answer:
Debouncing ensures a function executes only after a specified delay has passed since the
last time it was called. It is often used in search boxes or resize events to prevent excessive
function calls.

For Example:

const debounce = (func, delay) => {


let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => func.apply(this, args), delay);
};
};

const logInput = debounce((input) => console.log(input), 500);

document.querySelector('#searchBox').addEventListener('input', (e) => {


logInput(e.target.value);
});

Here, the logInput function executes 500ms after the user stops typing.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
78

26. What is throttling, and how is it different from debouncing?

Answer:
Throttling ensures a function runs at regular intervals, even if it’s triggered continuously. This
is useful for scrolling or button click events to limit the frequency of execution.

For Example:

const throttle = (func, interval) => {


let lastTime = 0;
return (...args) => {
const now = Date.now();
if (now - lastTime >= interval) {
func.apply(this, args);
lastTime = now;
}
};
};

const logScroll = throttle(() => console.log('Scrolled'), 1000);

window.addEventListener('scroll', logScroll);

Here, the logScroll function executes at most once every second, even if the scroll event
fires continuously.

27. How does JavaScript handle memory leaks?

Answer:
JavaScript has automatic garbage collection, but memory leaks can still occur if references
to objects are not released. Common causes include:

● Global variables that persist.


● Unremoved event listeners.
● Circular references between objects.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
79

const button = document.querySelector('#leakButton');


function handleClick() {
console.log('Button clicked');
}
button.addEventListener('click', handleClick);

// Fix: Remove the listener when not needed


button.removeEventListener('click', handleClick);

Not removing the event listener keeps the reference in memory, causing a memory leak.

28. What are JavaScript modules, and how do you use them?

Answer:
JavaScript modules allow code to be split into reusable files, improving code maintainability.
You can export functions, variables, or objects from one file and import them into another.

For Example:

// math.js (module)
export function add(a, b) {
return a + b;
}

// main.js
import { add } from './math.js';
console.log(add(2, 3)); // Output: 5

Here, the add function is exported from math.js and imported into main.js.

29. How do you manage asynchronous code using Promise.all()?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
80

Answer:
Promise.all() executes multiple Promises in parallel and resolves when all Promises are
fulfilled. If any Promise fails, Promise.all() rejects with the error.

For Example:

const fetchPosts = fetch('https://jsonplaceholder.typicode.com/posts');


const fetchUsers = fetch('https://jsonplaceholder.typicode.com/users');

Promise.all([fetchPosts, fetchUsers])
.then(responses => Promise.all(responses.map(res => res.json())))
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

Here, both API calls are executed simultaneously. If both resolve, the results are logged;
otherwise, the error is caught.

30. How do you use Web Workers in JavaScript for multithreading?

Answer:
JavaScript is single-threaded, but Web Workers allow you to run code in background
threads. This prevents the UI from freezing during long computations.

For Example:

// worker.js (Web Worker)


self.onmessage = function (event) {
const result = event.data * 2;
postMessage(result);
};

// main.js
const worker = new Worker('worker.js');
worker.postMessage(5);

worker.onmessage = function (event) {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
81

console.log('Result from worker:', event.data); // Output: 10


};

In this example, the Web Worker performs a calculation in the background and sends the
result back to the main thread. This ensures smooth UI performance.

31. What is the difference between Map and Object in JavaScript?

Answer:
Both Map and Object store key-value pairs, but they have different use cases and behaviors:

● Object: Keys must be strings or symbols. Useful for structured data.


● Map: Keys can be of any type (including objects, functions). Maintains insertion order.

For Example:

const obj = { key: 'value' };


const map = new Map();
map.set('key', 'value');
map.set({ id: 1 }, 'User');

console.log(obj.key); // Output: value


console.log(map.get('key')); // Output: value
console.log(map.size); // Output: 2

Here, the Map stores a non-string key, which is not possible with Object.

32. How does the this keyword behave in JavaScript?

Answer:
The value of this depends on the context in which a function is called:

● In global scope, this refers to the global window object (in browsers).

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
82

● In object methods, this refers to the object.


● Inside arrow functions, this retains the value from its enclosing context.

For Example:

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

obj.greet();

const arrowFunc = () => console.log(this); // Logs the window object


arrowFunc();

Here, the regular function uses this as the object (obj), while the arrow function keeps this
from the outer scope.

33. What are generators in JavaScript, and how do they work?

Answer:
A generator function is declared with function* and can pause its execution using the
yield keyword. Generators are useful for creating iterators.

For Example:

function* numberGenerator() {
yield 1;
yield 2;
yield 3;
}

const gen = numberGenerator();


console.log(gen.next().value); // 1

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
83

console.log(gen.next().value); // 2
console.log(gen.next().value); // 3

Each call to gen.next() returns the next value in the sequence.

34. What is the difference between call, apply, and bind?

Answer:
These methods allow you to change the context of this in a function:

● call(): Invokes the function immediately with a specified this value and arguments.
● apply(): Similar to call(), but arguments are passed as an array.
● bind(): Returns a new function with a bound this value.

For Example:

const person = { name: 'Shardul' };

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

greet.call(person, 'Hello'); // Output: Hello, Shardul


greet.apply(person, ['Hi']); // Output: Hi, Shardul

const boundGreet = greet.bind(person);


boundGreet('Hey'); // Output: Hey, Shardul

35. How does JavaScript handle prototypes and inheritance?

Answer:
JavaScript uses prototypes for inheritance. Every object has a hidden property called
__proto__, which points to its prototype. Methods and properties are inherited through the
prototype chain.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
84

For Example:

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

Person.prototype.greet = function () {
console.log(`Hello, ${this.name}`);
};

const user = new Person('Shardul');


user.greet(); // Output: Hello, Shardul

Here, the user object inherits the greet method from Person.prototype.

36. How do you implement deep cloning in JavaScript?

Answer:
Deep cloning creates a complete copy of an object, including nested objects, unlike shallow
cloning.

For Example:

const obj = { name: 'Shardul', address: { city: 'Pune' } };


const deepClone = JSON.parse(JSON.stringify(obj));

deepClone.address.city = 'Mumbai';
console.log(obj.address.city); // Output: Pune

Here, the original object remains unchanged because we used deep cloning.

37. What is the module pattern in JavaScript?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
85

Answer:
The module pattern allows you to create private variables and public methods by using
closures. It helps in organizing code and encapsulating logic.

For Example:

const Counter = (function () {


let count = 0;

return {
increment() {
count++;
console.log(count);
},
reset() {
count = 0;
console.log('Counter reset');
},
};
})();

Counter.increment(); // 1
Counter.increment(); // 2
Counter.reset(); // Counter reset

Here, the count variable is private, accessible only through public methods.

38. How does JavaScript handle hoisting?

Answer:
Hoisting allows function and variable declarations to be moved to the top of their scope
before code execution. However, only the declaration is hoisted, not the initialization.

For Example:

console.log(a); // Output: undefined

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
86

var a = 5;

hoistedFunction(); // Output: Hoisted!


function hoistedFunction() {
console.log('Hoisted!');
}

Variables declared with let and const are hoisted but cannot be accessed before declaration
(temporal dead zone).

39. What are WeakMap and WeakSet in JavaScript?

Answer:
WeakMap and WeakSet hold weak references to objects, meaning the garbage collector can
reclaim the memory if there are no other references. They do not prevent memory leaks and
are useful in managing memory.

For Example:

let obj = { id: 1 };


const weakMap = new WeakMap();
weakMap.set(obj, 'Data');

obj = null; // Now the object can be garbage collected

In this example, once obj is set to null, the garbage collector can reclaim it because WeakMap
holds a weak reference.

40. How do you implement a custom iterator in JavaScript?

Answer:
A custom iterator allows an object to be iterated using the for...of loop by implementing
the [Symbol.iterator] method.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
87

const myIterable = {
data: [1, 2, 3],
[Symbol.iterator]() {
let index = 0;
return {
next: () => ({
value: this.data[index++],
done: index > this.data.length,
}),
};
},
};

for (const value of myIterable) {


console.log(value);
}

// Output:
// 1
// 2
// 3

Here, we created an object with a custom iterator that allows it to be traversed using
for...of.

SCENARIO QUESTIONS
41. Scenario: Changing the content of an HTML element dynamically.

Question: How can you use JavaScript to modify the content of an HTML element using
innerHTML?

Answer:
To modify the content of an HTML element, innerHTML is used. It updates the HTML
structure or text within the selected element. This is helpful when you need to change the
display based on user input, an event, or a data update.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
88

For Example:

// HTML: <div id="greeting"></div>

// JavaScript
const greetingDiv = document.getElementById('greeting');
greetingDiv.innerHTML = '<h1>Hello, Welcome to the Page!</h1>';

If this example were displayed on a webpage, the resulting HTML structure would be:

Tag Content

<div> <h1>Hello, Welcome!</h1>

This structure shows the div element containing a header (h1), which is inserted dynamically
using JavaScript.

42. Scenario: Selecting multiple elements with a common class.

Question: How can you use JavaScript to apply styles to multiple elements with the same
class name?

Answer:
Using querySelectorAll(), you can select all elements with a specific class name. It returns
a NodeList, which behaves like an array. You can use forEach() to iterate over the elements
and apply styles.

For Example:

// HTML: <p class="highlight">Text 1</p><p class="highlight">Text 2</p>

// JavaScript
const highlights = document.querySelectorAll('.highlight');
highlights.forEach(paragraph => {
paragraph.style.color = 'red';

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
89

});

Resulting Table:

Element Content Applied Style

<p> Text 1 Color: Red

<p> Text 2 Color: Red

43. Scenario: Responding to a button click event.

Question: How can you use addEventListener to trigger an alert when a button is clicked?

Answer:
The addEventListener() method lets you attach multiple event handlers to an element
without overriding existing ones. It also separates JavaScript behavior from HTML structure,
following best practices.

For Example:

// HTML: <button id="alertButton">Click Me</button>

// JavaScript
const button = document.getElementById('alertButton');
button.addEventListener('click', () => {
alert('Button was clicked!');
});

After clicking the button, the user will see an alert dialog with the message "Button was
clicked!".

44. Scenario: Storing user data temporarily during a session.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
90

Question: How can you use sessionStorage to store and retrieve data?

Answer:
The sessionStorage object stores data temporarily for the duration of the session. It is
cleared when the tab or browser is closed, making it useful for temporary data, such as form
progress.

For Example:

// Store data in sessionStorage


sessionStorage.setItem('username', 'Shardul');

// Retrieve data from sessionStorage


const username = sessionStorage.getItem('username');
console.log(username); // Output: Shardul

Resulting Table:

Key Value Storage Type

username Shardul SessionStorage

45. Scenario: Displaying a message after a delay.

Question: How can you use setTimeout to show a message after 3 seconds?

Answer:
The setTimeout() function executes a specified function after a given delay. This can be
used for delayed notifications, animations, or loading indicators.

For Example:

setTimeout(() => {
console.log('Hello! This message appears after 3 seconds.');
}, 3000);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
91

After 3 seconds, the console will display:


"Hello! This message appears after 3 seconds."

46. Scenario: Handling multiple click events efficiently.

Question: How can you use event delegation to handle clicks on dynamically added list
items?

Answer:
With event delegation, you add an event listener to a parent element, allowing it to handle
events for dynamically added child elements. This improves performance by avoiding
multiple listeners.

For Example:

// HTML: <ul id="itemList"></ul>

// JavaScript
const itemList = document.getElementById('itemList');
itemList.addEventListener('click', event => {
if (event.target.tagName === 'LI') {
alert(`You clicked on ${event.target.textContent}`);
}
});

// Dynamically adding a new list item


const newItem = document.createElement('li');
newItem.textContent = 'New Item';
itemList.appendChild(newItem);

After clicking on the new <li> item, an alert will show the clicked item's text.

47. Scenario: Managing the browser’s local storage.

Question: How can you store and retrieve a value using localStorage?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
92

Answer:
The localStorage object stores key-value pairs with no expiration date, making it ideal for
saving user preferences.

For Example:

// Store data in localStorage


localStorage.setItem('theme', 'dark');

// Retrieve data from localStorage


const theme = localStorage.getItem('theme');
console.log(theme); // Output: dark

Resulting Table:

Key Value Storage Type

theme dark LocalStorage

48. Scenario: Preventing the default form submission behavior.

Question: How can you prevent a form from submitting using event.preventDefault()?

Answer:
The event.preventDefault() method prevents the default behavior of an event, like form
submission, to allow custom validation or actions.

For Example:

// HTML: <form id="myForm"><button type="submit">Submit</button></form>

// JavaScript
const form = document.getElementById('myForm');
form.addEventListener('submit', event => {
event.preventDefault();

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
93

console.log('Form submission prevented');


});

Now, clicking the submit button will not submit the form but log the message "Form
submission prevented".

49. Scenario: Fetching data from an API.

Question: How can you use the Fetch API to retrieve data from a server?

Answer:
The Fetch API provides a modern way to make HTTP requests and returns a Promise. It’s
used for GET, POST, PUT, or DELETE requests.

For Example:

fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

Resulting Data in Console:

{
"userId": 1,
"id": 1,
"title": "Sample Post",
"body": "This is a sample post content."
}

50. Scenario: Handling event propagation with bubbling and capturing.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
94

Question: How can you demonstrate event bubbling and capturing in JavaScript?

Answer:
Event bubbling propagates the event from the target element to its ancestors, while
capturing flows from ancestors to the target. Both can be demonstrated with
addEventListener().

For Example:

// HTML: <div id="parent"><button id="child">Click Me</button></div>

// JavaScript
const parent = document.getElementById('parent');
const child = document.getElementById('child');

// Event bubbling (default)


parent.addEventListener('click', () => console.log('Parent clicked'));
child.addEventListener('click', () => console.log('Child clicked'));

// Event capturing
parent.addEventListener(
'click',
() => console.log('Parent capturing'),
true
);

Result When Button is Clicked:

Event Phase Element Message Logged

Capturing Parent Parent capturing

Bubbling Child Child clicked

Bubbling Parent Parent clicked

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
95

This table shows how both bubbling and capturing occur when the button is clicked.

51. Scenario: Accessing an element by ID.

Question: How do you use getElementById to select an element in the DOM?

Answer:
The getElementById() method selects an HTML element using its unique ID. This is the
most efficient way to access a single element because IDs are unique within the DOM. After
selecting the element, you can read or modify its properties and content.

For Example:

// HTML: <h1 id="title">Welcome</h1>

// JavaScript
const titleElement = document.getElementById('title');
console.log(titleElement.innerHTML); // Output: Welcome

Resulting Table:

Property Value

Tag <h1>

ID title

Content Welcome

52. Scenario: Changing CSS styles dynamically.

Question: How can you use JavaScript to modify an element’s CSS styles?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
96

Answer:
The style property allows you to dynamically modify an element’s inline CSS styles. This is
helpful when you need to change an element's appearance in response to user actions, such
as hovering, clicking, or form submissions.

For Example:

// HTML: <div id="box" style="width: 100px; height: 100px;"></div>

// JavaScript
const box = document.getElementById('box');
box.style.backgroundColor = 'blue';
box.style.border = '2px solid black';

Resulting Table:

Property Value

backgroundColor blue

border 2px solid black

The <div> element now has a blue background and a black border.

53. Scenario: Adding and removing CSS classes.

Question: How do you add or remove CSS classes using the classList property?

Answer:
The classList property provides methods to add, remove, toggle, or check CSS classes on
an element. This is useful when changing the visual state of an element dynamically without
directly modifying the styles.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
97

// HTML: <div id="box"></div>

// JavaScript
const box = document.getElementById('box');
box.classList.add('highlight'); // Adds 'highlight' class
box.classList.remove('highlight'); // Removes 'highlight' class
box.classList.toggle('active'); // Toggles 'active' class

Resulting Table:

Action Class Name Applied

Add 'highlight' highlight Yes

Remove 'highlight' highlight No

Toggle 'active' active Depends

54. Scenario: Creating new HTML elements dynamically.

Question: How can you create and add new elements to the DOM using JavaScript?

Answer:
You can use document.createElement() to create a new HTML element and appendChild()
or append() to add it to the DOM. This method is useful when content needs to be generated
dynamically based on user actions or API responses.

For Example:

// JavaScript
const newElement = document.createElement('p');
newElement.textContent = 'This is a new paragraph.';
document.body.appendChild(newElement);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
98

Resulting Table:

Tag Content

<p> This is a new paragraph.

The <p> element is added to the end of the <body> with the given content.

55. Scenario: Detecting keypress events.

Question: How do you detect when a user presses a key using JavaScript?

Answer:
The keydown and keyup events detect when a key is pressed or released. These events are
commonly used to build features like input validation or keyboard shortcuts.

For Example:

document.addEventListener('keydown', (event) => {


console.log(`Key pressed: ${event.key}`);
});

When a key is pressed, the console will log the corresponding key's name.

56. Scenario: Automatically refreshing content at intervals.

Question: How do you use setInterval to refresh content periodically?

Answer:
The setInterval() function executes a specified function at fixed intervals (in milliseconds).
This is useful for real-time updates, such as clocks or dashboards.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
99

setInterval(() => {
console.log('This message logs every 2 seconds.');
}, 2000);

Resulting Table (First 6 Seconds):

Time (seconds) Output

2 This message logs every 2 seconds.

4 This message logs every 2 seconds.

6 This message logs every 2 seconds.

57. Scenario: Canceling a scheduled task.

Question: How can you use clearTimeout or clearInterval to cancel a scheduled task?

Answer:
The clearTimeout() and clearInterval() methods cancel tasks that were scheduled with
setTimeout() or setInterval(). This is useful to prevent unnecessary operations when they
are no longer needed.

For Example:

const intervalId = setInterval(() => {


console.log('Running...');
}, 1000);

setTimeout(() => {
clearInterval(intervalId);
console.log('Interval cleared');
}, 5000);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
100

Resulting Table:

Time (seconds) Output

1 Running...

2 Running...

3 Running...

4 Running...

5 Interval cleared

58. Scenario: Working with cookies.

Question: How can you create, read, and delete cookies in JavaScript?

Answer:
Cookies store small pieces of data in the browser. You can set, read, or delete cookies using
the document.cookie API.

For Example:

// Setting a cookie
document.cookie = "user=Shardul; expires=Fri, 31 Dec 2024 12:00:00 UTC";

// Reading cookies
console.log(document.cookie); // Output: user=Shardul

// Deleting a cookie by setting an expired date


document.cookie = "user=Shardul; expires=Thu, 01 Jan 1970 00:00:00 UTC";

Resulting Table (After Setting):

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
101

Name Value Expiration

user Shardul Fri, 31 Dec 2024 12:00:00 UTC

59. Scenario: Detecting the window size.

Question: How do you detect changes in the browser window’s size?

Answer:
The resize event detects when the browser window is resized. You can use it to make your
application responsive by adjusting layout or components dynamically.

For Example:

window.addEventListener('resize', () => {
console.log(`Window size: ${window.innerWidth}x${window.innerHeight}`);
});

Resulting Table:

Action Output Example

Resize window Window size: 800x600

Resize again Window size: 1024x768

60. Scenario: Preventing event bubbling.

Question: How do you stop event bubbling in JavaScript?

Answer:
Event bubbling propagates events from the target element to its parent elements. You can
stop this propagation using event.stopPropagation().

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
102

For Example:

// HTML: <div id="parent"><button id="child">Click Me</button></div>

// JavaScript
const parent = document.getElementById('parent');
const child = document.getElementById('child');

parent.addEventListener('click', () => console.log('Parent clicked'));


child.addEventListener('click', (event) => {
event.stopPropagation();
console.log('Child clicked');
});

Resulting Table:

Click Target Output

Child Button Child clicked

Parent Div (No output due to stopPropagation)

This ensures only the child’s click event is logged, as the propagation is stopped.

61. Scenario: Creating a promise-based API call.

Question: How can you use Promises to handle asynchronous API calls?

Answer:
Promises simplify handling asynchronous operations. With fetch(), you can make an API
call that returns a promise. This promise resolves with the response if the request is
successful or rejects if there is an error.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
103

const getPost = (postId) => {


return new Promise((resolve, reject) => {
fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`)
.then((response) => {
if (!response.ok) reject('Error fetching post');
return response.json();
})
.then((data) => resolve(data))
.catch((error) => reject(error));
});
};

getPost(1)
.then((data) => console.log(data))
.catch((error) => console.error(error));

Resulting Table (API Response):

Field Value

userId 1

id 1

title Sample Post Title

body This is a sample post body.

62. Scenario: Handling asynchronous code with async/await.

Question: How can you use async/await to simplify promise handling?

Answer:
async/await makes asynchronous code easier to read and write by eliminating the need for

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
104

chained .then() calls. An async function returns a promise, and await pauses execution
until the promise resolves.

For Example:

const getPostAsync = async (postId) => {


try {
const response = await
fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`);
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Error:', error);
}
};

getPostAsync(1);

Resulting Table (API Response):

Field Value

userId 1

id 1

title Sample Post Title

body This is a sample post body.

63. Scenario: Managing multiple promises.

Question: How can you handle multiple promises using Promise.all()?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
105

Answer:
Promise.all() runs multiple promises in parallel and waits for all of them to resolve. If any
promise is rejected, the entire Promise.all() call fails.

For Example:

const fetchPosts = fetch('https://jsonplaceholder.typicode.com/posts');


const fetchUsers = fetch('https://jsonplaceholder.typicode.com/users');

Promise.all([fetchPosts, fetchUsers])
.then((responses) => Promise.all(responses.map((res) => res.json())))
.then((data) => console.log('Data:', data))
.catch((error) => console.error('Error:', error));

Resulting Table (Data from Both APIs):

Resource Count

Posts 100

Users 10

64. Scenario: Implementing a debounce function.

Question: How can you implement debouncing to limit function execution?

Answer:
Debouncing ensures that a function executes only after a specified delay. This is helpful for
actions like search input where you want to wait for the user to stop typing before making
an API call.

For Example:

const debounce = (func, delay) => {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
106

let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), delay);
};
};

const logInput = debounce((input) => console.log(input), 500);

document.getElementById('searchBox').addEventListener('input', (e) => {


logInput(e.target.value);
});

Resulting Table (Input & Execution Timing):

Input Execution After Action

"Hello" 500ms Log "Hello"

"JavaScript" 500ms Log "JavaScript"

65. Scenario: Implementing throttling.

Question: How can you implement throttling to control function execution?

Answer:
Throttling limits a function's execution to once every specified interval. It’s useful for actions
like scroll events to improve performance.

For Example:

const throttle = (func, interval) => {


let lastTime = 0;
return (...args) => {
const now = Date.now();

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
107

if (now - lastTime >= interval) {


func.apply(this, args);
lastTime = now;
}
};
};

const handleScroll = throttle(() => console.log('Scrolled!'), 1000);

window.addEventListener('scroll', handleScroll);

Resulting Table (Scroll Events):

Time (seconds) Action

1 Scrolled!

2 (Ignored)

3 Scrolled!

66. Scenario: Creating a custom event.

Question: How can you create and dispatch a custom event in JavaScript?

Answer:
You can create a custom event using the CustomEvent constructor and trigger it using
dispatchEvent().

For Example:

const myEvent = new CustomEvent('myCustomEvent', {


detail: { message: 'Hello from custom event!' },
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
108

document.addEventListener('myCustomEvent', (event) => {


console.log(event.detail.message);
});

document.dispatchEvent(myEvent);

Resulting Output:

Hello from custom event!

67. Scenario: Implementing a closure.

Question: How does JavaScript use closures to retain access to variables?

Answer:
A closure is when a function retains access to variables from its outer scope, even after the
outer function has finished executing.

For Example:

function createCounter() {
let count = 0;
return () => {
count++;
console.log(`Count: ${count}`);
};
}

const counter = createCounter();


counter(); // Count: 1
counter(); // Count: 2

Resulting Table (Counter State):

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
109

Call Output

counter() Count: 1

counter() Count: 2

68. Scenario: Using bind() to change this context.

Question: How does bind() change the this context of a function?

Answer:
The bind() method creates a new function with a specified this value, which is useful when
passing functions as callbacks.

For Example:

const person = {
name: 'Shardul',
greet: function () {
console.log(`Hello, ${this.name}`);
},
};

const greetBound = person.greet.bind({ name: 'JavaScript' });


greetBound(); // Output: Hello, JavaScript

Resulting Table:

Original this New this Value Output

person {name: 'JavaScript'} Hello, JavaScript

69. Scenario: Using call() and apply().

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
110

Question: How do call() and apply() invoke functions with a specific this context?

Answer:
call() and apply() both invoke a function with a specific this value, but call() accepts
arguments individually, while apply() accepts them as an array.

For Example:

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

const person = { name: 'Shardul' };

greet.call(person, 'Hello'); // Output: Hello, Shardul


greet.apply(person, ['Hi']); // Output: Hi, Shardul

Resulting Table:

Method Arguments Format Output

call Individual Hello, Shardul

apply Array Hi, Shardul

70. Scenario: Using a Web Worker.

Question: How can you use Web Workers to perform background tasks?

Answer:
Web Workers run scripts in the background, allowing you to perform heavy computations
without blocking the UI thread.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
111

// worker.js
self.onmessage = function (e) {
const result = e.data * 2;
postMessage(result);
};

// main.js
const worker = new Worker('worker.js');
worker.postMessage(5);

worker.onmessage = function (e) {


console.log('Result from worker:', e.data); // Output: 10
};

Resulting Table:

Input Computation Output

5 5*2 10

This demonstrates how Web Workers can offload computational tasks to avoid blocking the
UI.

71. Scenario: Implementing an image lazy loader.

Question: How can you implement lazy loading of images using the IntersectionObserver
API?

Answer:
Lazy loading delays loading images until they enter the viewport. This improves page
performance, especially for pages with many images. The IntersectionObserver API
detects when an element becomes visible in the viewport and triggers loading.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
112

// HTML: <img data-src="image.jpg" class="lazy" alt="Lazy Loaded Image">

// JavaScript
const lazyImages = document.querySelectorAll('.lazy');

const observer = new IntersectionObserver((entries) => {


entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img); // Stop observing once loaded
}
});
});

lazyImages.forEach((img) => observer.observe(img));

Resulting Table:

Image State Action

Not in viewport Do not load image

In viewport Load image from data-src

72. Scenario: Deep cloning a nested object.

Question: How can you perform a deep clone of an object in JavaScript?

Answer:
A deep clone creates an entirely new object, including all nested objects. Using
JSON.stringify() and JSON.parse() is a quick way to deep clone objects, although it
doesn’t work for functions or undefined values.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
113

const original = { name: 'Shardul', address: { city: 'Pune' } };


const deepClone = JSON.parse(JSON.stringify(original));

// Modifying the clone


deepClone.address.city = 'Mumbai';

console.log(original.address.city); // Output: Pune


console.log(deepClone.address.city); // Output: Mumbai

Resulting Table:

Property Original Value Cloned Value

city Pune Mumbai

73. Scenario: Flattening a nested array.

Question: How can you flatten a deeply nested array in JavaScript?

Answer:
You can use Array.prototype.flat() with Infinity as the depth or use recursion to flatten
a nested array.

For Example:

const nestedArray = [1, [2, [3, [4]]]];

// Using flat() with Infinity


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

Resulting Table:

Original Array Flattened Array

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
114

[1, [2, [3, [4]]]] [1, 2, 3, 4]

74. Scenario: Detecting browser information.

Question: How can you detect the user’s browser and platform using the navigator object?

Answer:
The navigator object provides information about the user's browser and platform. This is
useful for browser-specific behavior.

For Example:

console.log(navigator.userAgent); // Logs browser details


console.log(navigator.platform); // Logs platform (e.g., 'Win32')

Resulting Table:

Property Example Value

userAgent Mozilla/5.0 (Windows NT…)

platform Win32

75. Scenario: Implementing custom iterators.

Question: How can you create a custom iterator in JavaScript?

Answer:
A custom iterator allows an object to be used in a for...of loop by implementing the
[Symbol.iterator]() method.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
115

const myIterable = {
data: [1, 2, 3],
[Symbol.iterator]() {
let index = 0;
return {
next: () => ({
value: this.data[index++],
done: index > this.data.length,
}),
};
},
};

for (const value of myIterable) {


console.log(value);
}

Resulting Table:

Iteration Value

1 1

2 2

3 3

76. Scenario: Using WeakMap for memory-sensitive objects.

Question: How can you use WeakMap to store data without preventing garbage collection?

Answer:
A WeakMap holds weak references to objects, meaning they can be garbage collected if no
other references exist. This is useful for caching without causing memory leaks.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
116

let user = { id: 1, name: 'Shardul' };


const weakMap = new WeakMap();
weakMap.set(user, 'Active');

// Setting the object to null allows garbage collection


user = null;

console.log(weakMap); // Output: WeakMap { <items unknown> }

Resulting Table:

Key Value Status

user Active Garbage collected

77. Scenario: Implementing event throttling with requestAnimationFrame.

Question: How can you use requestAnimationFrame for throttling DOM updates?

Answer:
Using requestAnimationFrame() ensures that animations and updates are synchronized
with the browser’s refresh rate, reducing the number of DOM manipulations.

For Example:

let ticking = false;

window.addEventListener('scroll', () => {
if (!ticking) {
requestAnimationFrame(() => {
console.log('Scroll position:', window.scrollY);
ticking = false;
});
ticking = true;
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
117

});

Resulting Table:

Scroll Event Output (ScrollY)

1st Event 120

2nd Event 300

78. Scenario: Detecting focus and blur events on input elements.

Question: How can you handle focus and blur events using JavaScript?

Answer:
The focus and blur events detect when an input gains or loses focus. These events are useful
for form validation or styling.

For Example:

const input = document.getElementById('nameInput');

input.addEventListener('focus', () => console.log('Input focused'));


input.addEventListener('blur', () => console.log('Input lost focus'));

Resulting Table:

Event Output

focus Input focused

blur Input lost focus

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
118

79. Scenario: Implementing a scroll-to-top button.

Question: How can you create a button that scrolls the page to the top?

Answer:
You can use the window.scrollTo() method to smoothly scroll the page to the top when a
button is clicked.

For Example:

const button = document.getElementById('scrollTopButton');

button.addEventListener('click', () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
});

Resulting Table:

Action Scroll Position

Button Click 0 (Top of Page)

80. Scenario: Animating elements with CSS and JavaScript.

Question: How can you animate elements using CSS transitions and JavaScript?

Answer:
You can combine CSS transitions with JavaScript to animate elements dynamically, such as
moving or resizing them.

For Example:

// HTML: <div id="box" style="width: 100px; height: 100px;"></div>

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
119

// JavaScript
const box = document.getElementById('box');
box.style.transition = 'all 0.5s';

document.addEventListener('click', () => {
box.style.width = '200px';
box.style.height = '200px';
});

Resulting Table:

Action Width Height

Before Click 100px 100px

After Click 200px 200px

This example shows how the box smoothly resizes when the page is clicked.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
120

Chapter 3: Object-Oriented JavaScript (OOP)

THEORETICAL QUESTIONS
1. What is an object literal in JavaScript?

Answer:
An object literal is the simplest way to create an object in JavaScript. It consists of a comma-
separated list of key-value pairs enclosed in curly braces {}. This approach is often used when
you need a single object with a known set of properties and values. Object literals are highly
readable and allow quick creation of small objects without needing a constructor or class.

For Example:

const person = {
name: "John",
age: 30,
greet: function () {
console.log(`Hello, my name is ${this.name}.`);
}
};

person.greet(); // Output: Hello, my name is John.

Here, person is created as an object literal with properties name, age, and a greet() method.
The this keyword refers to the object calling the method (person), ensuring access to the
object’s properties within the method.

2. How do constructors work in JavaScript?

Answer:
A constructor function is a special function used to create multiple instances of an object
with similar properties. It allows you to initialize an object’s properties dynamically at
runtime. When you use the new keyword with a constructor function, it creates a new object

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
121

and sets the context of this to that new object. Constructors follow the convention of
naming functions with a capital first letter.

For Example:

function Person(name, age) {


this.name = name;
this.age = age;
this.greet = function () {
console.log(`Hello, I'm ${this.name} and I'm ${this.age} years old.`);
};
}

const person1 = new Person("Alice", 25);


person1.greet(); // Output: Hello, I'm Alice and I'm 25 years old.

Here, Person is a constructor function. person1 is a new object created using the new
Person() syntax, inheriting properties and methods defined within the function.

3. What is prototype-based inheritance?

Answer:
In JavaScript, every object has a hidden property called [[Prototype]], which refers to
another object. This is called prototype-based inheritance. When trying to access a property
that doesn't exist on the object itself, JavaScript looks up the prototype chain to find it. This
mechanism enables objects to reuse properties and methods from other objects, promoting
efficient memory usage and modular code.

For Example:

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

Animal.prototype.speak = function () {
console.log(`${this.type} makes a sound.`);
};

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
122

const dog = new Animal("Dog");


dog.speak(); // Output: Dog makes a sound.

In the example above, the speak() method is added to Animal.prototype, allowing all
instances created from Animal to share this method.

4. What is Object.create() used for?

Answer:
Object.create() is a built-in method that creates a new object with a specified prototype. It
is a more straightforward way to set up prototype-based inheritance, without needing
constructor functions. It gives more control over the prototype and properties of an object.

For Example:

const animalPrototype = {
speak: function () {
console.log(`${this.type} makes a sound.`);
}
};

const dog = Object.create(animalPrototype);


dog.type = "Dog";
dog.speak(); // Output: Dog makes a sound.

Here, dog is created with animalPrototype as its prototype, meaning it inherits the speak
method.

5. How do classes differ from constructor functions?

Answer:
Classes in JavaScript are syntactic sugar introduced in ES6 to make working with objects and
inheritance easier. While constructor functions serve the same purpose, classes provide a

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
123

clearer, more structured syntax. With classes, the concept of inheritance and object creation
becomes more readable.

For Example:

class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}

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

const person1 = new Person("Bob", 32);


person1.greet(); // Output: Hi, I'm Bob.

This example shows how the Person class is more organized and intuitive compared to
constructor functions.

6. What is the extends keyword in JavaScript?

Answer:
The extends keyword is used to create a class that inherits from another class. This helps in
reusing code and creating a hierarchy of objects. The child class inherits all properties and
methods of the parent class but can also have its own unique features.

For Example:

class Animal {
constructor(type) {
this.type = type;
}

sound() {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
124

console.log(`${this.type} makes a sound.`);


}
}

class Dog extends Animal {


constructor() {
super("Dog");
}
}

const myDog = new Dog();


myDog.sound(); // Output: Dog makes a sound.

Here, the Dog class extends Animal, inheriting its sound() method.

7. What are static methods in JavaScript classes?

Answer:
Static methods belong to the class itself, not the instances of the class. They are called using
the class name rather than an object created from the class. Static methods are often utility
functions that relate to the class but don't need to operate on individual instances.

For Example:

class MathUtils {
static add(a, b) {
return a + b;
}
}

console.log(MathUtils.add(5, 3)); // Output: 8

In the above example, add() is a static method that can be accessed using MathUtils
directly.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
125

8. How do getters and setters work in JavaScript?

Answer:
Getters and setters allow you to encapsulate object properties by controlling access to
them. A getter retrieves the value of a property, and a setter modifies it. Using getters and
setters ensures that any logic or validation can be implemented when properties are
accessed or changed.

For Example:

class Person {
constructor(name) {
this._name = name;
}

get name() {
return this._name;
}

set name(newName) {
if (newName) {
this._name = newName;
}
}
}

const person = new Person("Alice");


console.log(person.name); // Output: Alice
person.name = "Bob";
console.log(person.name); // Output: Bob

Here, the _name property is accessed and modified using the name getter and setter.

9. What is implicit binding in JavaScript?

Answer:
Implicit binding happens when the this keyword inside a function refers to the object that

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
126

invoked the function. This occurs most often when functions are called as methods on an
object. The value of this is set to the object to the left of the dot operator.

For Example:

const person = {
name: "Charlie",
greet() {
console.log(`Hi, I'm ${this.name}.`);
}
};

person.greet(); // Output: Hi, I'm Charlie.

Here, this refers to the person object because it is invoking the greet() function.

10. What is explicit binding in JavaScript?

Answer:
Explicit binding allows you to explicitly set the value of this when calling a function using
the call(), apply(), or bind() methods. This is useful when you want to control the context
of this in a function regardless of how it is invoked.

For Example:

function greet() {
console.log(`Hi, I'm ${this.name}.`);
}

const person = { name: "Dave" };

greet.call(person); // Output: Hi, I'm Dave.

Here, call() ensures that this inside the greet() function refers to the person object, even
though greet is not a method of person.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
127

11. What is the difference between call(), apply(), and bind() in


JavaScript?

Answer:
All three methods—call(), apply(), and bind()—are used to explicitly set the value of this
inside a function. However, they differ in how they invoke the function and how arguments
are passed.

● call(): Invokes the function immediately and accepts arguments individually.


● apply(): Invokes the function immediately but accepts arguments as an array.
● bind(): Returns a new function with the specified this value and optional
arguments, but does not invoke it immediately.

For Example:

function introduce(city, country) {


console.log(`Hi, I'm ${this.name} from ${city}, ${country}.`);
}

const person = { name: "Alice" };

introduce.call(person, "Paris", "France"); // Output: Hi, I'm Alice from Paris,


France.
introduce.apply(person, ["New York", "USA"]); // Output: Hi, I'm Alice from New
York, USA.

const boundFunction = introduce.bind(person, "Tokyo", "Japan");


boundFunction(); // Output: Hi, I'm Alice from Tokyo, Japan.

12. What is the difference between arrow functions and regular functions?

Answer:
Arrow functions (=>) were introduced in ES6 and differ from regular functions in several key
ways. The primary difference is that arrow functions do not have their own this binding.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
128

Instead, they inherit this from the lexical scope (the surrounding context). Arrow functions
also cannot be used as constructors and lack their own arguments object.

For Example:

const person = {
name: "Bob",
greet: function () {
console.log(`Hello, my name is ${this.name}.`); // Regular function: `this`
refers to `person`
},
arrowGreet: () => {
console.log(`Hello, my name is ${this.name}.`); // Arrow function: `this` is
inherited from the outer scope
}
};

person.greet(); // Output: Hello, my name is Bob.


person.arrowGreet(); // Output: Hello, my name is undefined.

In this example, the arrow function inherits this from its lexical scope, which isn't the person
object.

13. What is a prototype chain?

Answer:
The prototype chain is a series of linked objects that JavaScript follows to find properties or
methods. When accessing a property on an object, JavaScript first checks the object itself. If
the property is not found, it looks at the object's prototype, and so on, until the chain ends
with null.

For Example:

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

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
129

Animal.prototype.speak = function () {
console.log(`${this.type} makes a sound.`);
};

const dog = new Animal("Dog");

console.log(dog.hasOwnProperty("type")); // true
console.log(dog.hasOwnProperty("speak")); // false, as it's inherited from the
prototype
dog.speak(); // Output: Dog makes a sound.

In this example, speak() is found on the Animal.prototype, not directly on dog.

14. What is the significance of super() in JavaScript?

Answer:
The super() function is used within a subclass to call the constructor of the parent class. This
allows the child class to inherit properties from the parent. You must call super() before
accessing this in the child constructor.

For Example:

class Animal {
constructor(type) {
this.type = type;
}
}

class Dog extends Animal {


constructor(name) {
super("Dog"); // Calls the parent constructor
this.name = name;
}
}

const myDog = new Dog("Buddy");


console.log(myDog); // Output: Dog { type: 'Dog', name: 'Buddy' }

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
130

15. What happens if you don’t use new with a constructor function?

Answer:
If a constructor function is called without the new keyword, the this inside the function will
not refer to the new object. Instead, it will point to the global object (window in browsers). This
can lead to unexpected behavior or errors.

For Example:

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

const person = Person("Alice"); // Incorrect usage, no `new`


console.log(person); // Output: undefined
console.log(window.name); // Output: Alice (in browsers)

To avoid such issues, always use new when calling a constructor function.

16. What is method chaining in JavaScript?

Answer:
Method chaining is a technique where multiple methods are called sequentially on the
same object. Each method returns the object itself (or another object), enabling further calls
in a single statement. This makes code more concise and expressive.

For Example:

class Calculator {
constructor(value = 0) {
this.value = value;
}

add(num) {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
131

this.value += num;
return this;
}

subtract(num) {
this.value -= num;
return this;
}

multiply(num) {
this.value *= num;
return this;
}
}

const result = new Calculator(10).add(5).subtract(2).multiply(3).value;


console.log(result); // Output: 39

17. How do you create a singleton object in JavaScript?

Answer:
A singleton is a design pattern where only one instance of an object is created and shared
across the application. This can be achieved using an IIFE (Immediately Invoked Function
Expression) or an object literal.

For Example:

const Singleton = (function () {


let instance;

function createInstance() {
return { message: "I am the only instance" };
}

return {
getInstance: function () {
if (!instance) {
instance = createInstance();

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
132

}
return instance;
}
};
})();

const instance1 = Singleton.getInstance();


const instance2 = Singleton.getInstance();

console.log(instance1 === instance2); // Output: true

18. What is the purpose of Object.freeze()?

Answer:
Object.freeze() is a method that prevents modifications to an object. After freezing, you
cannot add, delete, or change properties. It ensures that the object remains immutable.

For Example:

const person = { name: "Alice", age: 25 };


Object.freeze(person);

person.age = 30; // This will have no effect


delete person.name; // This will also have no effect

console.log(person); // Output: { name: 'Alice', age: 25 }

19. What is the difference between shallow copy and deep copy?

Answer:
A shallow copy copies only the top-level properties, while a deep copy copies all nested
objects and properties. Shallow copies can lead to unexpected behavior if the original object
contains references to other objects.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
133

const original = { name: "Alice", address: { city: "New York" } };


const shallowCopy = { ...original };

shallowCopy.address.city = "Los Angeles";


console.log(original.address.city); // Output: Los Angeles (because it's a shallow
copy)

A deep copy can be created using structuredClone() or custom logic.

20. What are mixins in JavaScript?

Answer:
Mixins are a way to add reusable functionality to multiple objects or classes. Instead of using
inheritance, mixins copy properties and methods into other objects, promoting code reuse.

For Example:

const sayHiMixin = {
sayHi() {
console.log(`Hi, my name is ${this.name}.`);
}
};

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

Object.assign(Person.prototype, sayHiMixin);

const person = new Person("Alice");


person.sayHi(); // Output: Hi, my name is Alice.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
134

21. What is the concept of closures, and how do they relate to object-
oriented programming in JavaScript?

Answer:
A closure is formed when a function retains access to variables from its outer scope, even
after the outer function has completed execution. Closures are commonly used in object-
oriented programming to encapsulate data, mimicking private variables. This is useful for
creating data hiding and ensuring the internal state of an object cannot be accessed directly
from the outside.

For Example:

function Counter() {
let count = 0;

return {
increment: function () {
count++;
console.log(`Count: ${count}`);
},
getCount: function () {
return count;
}
};
}

const counter = Counter();


counter.increment(); // Output: Count: 1
counter.increment(); // Output: Count: 2
console.log(counter.getCount()); // Output: 2

Here, count is private, accessible only through the returned methods, demonstrating how
closures provide encapsulation.

22. How does the new keyword work internally in JavaScript?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
135

Answer:
When the new keyword is used, JavaScript performs several steps internally to create a new
object from a constructor function:

1. A new empty object is created: {}.


2. The new object’s [[Prototype]] is set to the constructor’s prototype.
3. The constructor function is called with this pointing to the new object.
4. If the constructor returns a non-primitive value, that value is returned; otherwise, the
newly created object is returned.

For Example:

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

const person = new Person("Alice");


console.log(person); // Output: Person { name: 'Alice' }

The new keyword ensures that the this inside the constructor points to the newly created
object.

23. What is the difference between polymorphism and inheritance in


JavaScript?

Answer:
Inheritance allows a child class or object to inherit properties and behaviors from a parent
class. Polymorphism allows a method to behave differently based on the context (e.g.,
different classes implementing the same method in their own way). In JavaScript,
polymorphism is often achieved through method overriding.

For Example:

class Animal {
speak() {
console.log("The animal makes a sound.");

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
136

}
}

class Dog extends Animal {


speak() {
console.log("The dog barks.");
}
}

const myDog = new Dog();


myDog.speak(); // Output: The dog barks.

Here, speak() is overridden in the Dog class, showing polymorphism.

24. How can you implement multiple inheritance using mixins?

Answer:
Since JavaScript does not natively support multiple inheritance, mixins allow us to copy
functionality from multiple sources into a class.

For Example:

const CanRun = {
run() {
console.log(`${this.name} is running.`);
}
};

const CanJump = {
jump() {
console.log(`${this.name} is jumping.`);
}
};

class Animal {
constructor(name) {
this.name = name;

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
137

}
}

Object.assign(Animal.prototype, CanRun, CanJump);

const rabbit = new Animal("Rabbit");


rabbit.run(); // Output: Rabbit is running.
rabbit.jump(); // Output: Rabbit is jumping.

Here, the Animal class acquires methods from both CanRun and CanJump.

25. What is the difference between prototypal and classical inheritance?

Answer:
In classical inheritance (as seen in languages like Java), classes define the structure and
behavior of objects. Prototypal inheritance in JavaScript relies on objects inheriting directly
from other objects via prototypes.

● Classical inheritance: Defined using classes (e.g., in ES6+ JavaScript).


● Prototypal inheritance: Objects inherit behavior from other objects through
prototypes.

For Example (Prototypal):

const animal = {
speak() {
console.log(`${this.name} makes a sound.`);
}
};

const dog = Object.create(animal);


dog.name = "Dog";
dog.speak(); // Output: Dog makes a sound.

26. How does JavaScript handle method overriding and overloading?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
138

Answer:
JavaScript supports method overriding but does not natively support method overloading
(same method with different parameter signatures). Method overriding occurs when a
subclass provides a specific implementation of a method already defined in the parent class.

For Example (Overriding):

class Animal {
sound() {
console.log("Some generic sound.");
}
}

class Dog extends Animal {


sound() {
console.log("The dog barks.");
}
}

const myDog = new Dog();


myDog.sound(); // Output: The dog barks.

For method overloading, you can implement it by checking the number or type of
parameters.

27. What are the advantages of encapsulation in JavaScript?

Answer:
Encapsulation refers to bundling data and methods within an object, hiding the internal
details from external access. This helps in:

● Protecting the internal state of objects.


● Reducing complexity by exposing only necessary details.
● Making the code easier to maintain and modify.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
139

class BankAccount {
#balance; // Private field

constructor(initialBalance) {
this.#balance = initialBalance;
}

deposit(amount) {
this.#balance += amount;
}

getBalance() {
return this.#balance;
}
}

const account = new BankAccount(100);


account.deposit(50);
console.log(account.getBalance()); // Output: 150

Here, #balance is a private field, ensuring it cannot be accessed from outside.

28. How does JavaScript implement private methods and fields?

Answer:
With the introduction of private fields in ES2022, JavaScript allows properties and methods
to be truly private using the # prefix. Private fields are only accessible within the class.

For Example:

class Counter {
#count = 0; // Private field

increment() {
this.#count++;
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
140

getCount() {
return this.#count;
}
}

const counter = new Counter();


counter.increment();
console.log(counter.getCount()); // Output: 1

Attempting to access #count directly from outside will result in an error.

29. How do you implement abstract classes in JavaScript?

Answer:
JavaScript does not have built-in support for abstract classes (classes that cannot be
instantiated and are meant to be extended). However, you can simulate them by throwing
an error in the constructor if someone tries to instantiate the base class directly.

For Example:

class Animal {
constructor() {
if (new.target === Animal) {
throw new Error("Cannot instantiate abstract class.");
}
}

speak() {
throw new Error("Method 'speak()' must be implemented.");
}
}

class Dog extends Animal {


speak() {
console.log("The dog barks.");
}
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
141

const myDog = new Dog();


myDog.speak(); // Output: The dog barks.

// const animal = new Animal(); // Error: Cannot instantiate abstract class.

30. What is the role of instanceof in JavaScript?

Answer:
The instanceof operator checks whether an object is an instance of a specific class or
constructor function, considering the object's prototype chain. It is useful for type checking
in JavaScript.

For Example:

class Animal {}
class Dog extends Animal {}

const myDog = new Dog();

console.log(myDog instanceof Dog); // Output: true


console.log(myDog instanceof Animal); // Output: true
console.log(myDog instanceof Object); // Output: true

Here, instanceof confirms that myDog is an instance of both Dog and Animal, showing the
inheritance hierarchy.

31. How does JavaScript handle inheritance for built-in objects like Array or
Error?

Answer:
JavaScript allows custom objects to inherit from built-in objects like Array and Error. This is
done using ES6 classes with the extends keyword. When inheriting from built-in objects, you
must call super() to properly initialize the parent object, ensuring the subclass behaves
correctly.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
142

For Example:

class CustomArray extends Array {


sum() {
return this.reduce((acc, value) => acc + value, 0);
}
}

const numbers = new CustomArray(1, 2, 3, 4);


console.log(numbers.sum()); // Output: 10
console.log(numbers instanceof Array); // Output: true

Here, CustomArray inherits from Array, and the subclass has access to all native array
methods as well as the new sum() method.

32. What are decorators in JavaScript, and how can they be used with
classes?

Answer:
Decorators are functions that modify the behavior of classes, methods, or properties. They
are a feature currently in stage 3 proposal and available in some environments. Decorators
wrap a class or method, adding additional functionality such as logging or validation.

For Example (with a decorator):

function logMethod(target, propertyKey, descriptor) {


const originalMethod = descriptor.value;
descriptor.value = function (...args) {
console.log(`Calling ${propertyKey} with`, args);
return originalMethod.apply(this, args);
};
return descriptor;
}

class Calculator {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
143

@logMethod
add(a, b) {
return a + b;
}
}

const calc = new Calculator();


console.log(calc.add(2, 3)); // Output: Calling add with [2, 3]
// 5

Here, the @logMethod decorator adds logging functionality to the add() method.

33. What is the difference between composition and inheritance?

Answer:
Composition and inheritance are two ways to create complex objects. Inheritance creates a
"is-a" relationship (e.g., a Dog is an Animal), while composition uses a "has-a" relationship (e.g.,
a Car has an Engine). Composition is often preferred over inheritance as it leads to more
flexible and maintainable code.

For Example (Composition):

class Engine {
start() {
console.log("Engine started.");
}
}

class Car {
constructor(engine) {
this.engine = engine;
}

drive() {
this.engine.start();
console.log("Car is driving.");
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
144

const myEngine = new Engine();


const myCar = new Car(myEngine);
myCar.drive(); // Output: Engine started.
// Car is driving.

34. What are the differences between public, private, and protected fields
in JavaScript?

Answer:
In JavaScript, public fields are accessible from anywhere, private fields (prefixed with #) are
accessible only within the class, and protected fields are not natively supported but can be
simulated with conventions like underscores (_).

For Example:

class Person {
#privateField = "This is private";

constructor(name) {
this.name = name; // Public field
}

getPrivateField() {
return this.#privateField;
}
}

const person = new Person("Alice");


console.log(person.name); // Output: Alice
console.log(person.getPrivateField()); // Output: This is private
// console.log(person.#privateField); // Error: Private field is not accessible

35. How does JavaScript handle the diamond problem in inheritance?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
145

Answer:
JavaScript does not natively support multiple inheritance, so it avoids the diamond problem
(a scenario where a child class inherits from two parents that share a common ancestor).
However, developers can use composition or mixins to simulate multiple inheritance
without conflicts.

36. How do you implement asynchronous methods in classes?

Answer:
You can define asynchronous methods inside a class using the async keyword. These
methods return a Promise and allow await inside them to handle asynchronous operations.

For Example:

class DataFetcher {
async fetchData() {
const response = await fetch("https://jsonplaceholder.typicode.com/todos/1");
const data = await response.json();
console.log(data);
}
}

const fetcher = new DataFetcher();


fetcher.fetchData(); // Output: { id: 1, title: "...", completed: ... }

37. How does JavaScript’s Object.assign() differ from the spread


operator?

Answer:
Both Object.assign() and the spread operator ({...}) are used to copy properties from
one or more source objects into a target object. However:

● Object.assign() performs a shallow copy and only copies enumerable properties.


● The spread operator is more concise but also performs a shallow copy.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
146

const obj1 = { a: 1 };
const obj2 = { b: 2 };

const merged1 = Object.assign({}, obj1, obj2);


const merged2 = { ...obj1, ...obj2 };

console.log(merged1); // Output: { a: 1, b: 2 }
console.log(merged2); // Output: { a: 1, b: 2 }

38. What is a factory function, and how is it different from a constructor


function?

Answer:
A factory function is a function that returns a new object, whereas a constructor function is
used with the new keyword to create objects. Factory functions offer more flexibility, such as
avoiding the need for this.

For Example:

function createPerson(name, age) {


return {
name,
age,
greet() {
console.log(`Hi, I'm ${name}.`);
}
};
}

const person = createPerson("Alice", 25);


person.greet(); // Output: Hi, I'm Alice.

39. How do you use polymorphism with interfaces in JavaScript?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
147

Answer:
JavaScript does not have interfaces like other languages, but polymorphism can be achieved
by ensuring different classes implement the same methods, allowing them to be used
interchangeably.

For Example:

class Animal {
speak() {
console.log("Animal speaks.");
}
}

class Dog extends Animal {


speak() {
console.log("The dog barks.");
}
}

class Cat extends Animal {


speak() {
console.log("The cat meows.");
}
}

const animals = [new Dog(), new Cat()];


animals.forEach(animal => animal.speak());
// Output: The dog barks.
// The cat meows.

40. What are proxy objects in JavaScript, and how can they be used?

Answer:
A proxy in JavaScript is an object that allows you to intercept and redefine fundamental
operations (e.g., property access). It is created using the Proxy constructor.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
148

const handler = {
get: function (target, property) {
return property in target ? target[property] : `Property ${property} not
found.`;
}
};

const person = { name: "Alice", age: 25 };


const proxyPerson = new Proxy(person, handler);

console.log(proxyPerson.name); // Output: Alice


console.log(proxyPerson.address); // Output: Property address not found.

Proxies are useful for tasks like validation, logging, and managing property access.

SCENARIO QUESTIONS

41. Scenario: Creating multiple objects with similar properties and methods
using object literals

Question: How would you create multiple objects with the same structure using object
literals? What are the limitations of this approach?

Answer:
When using object literals, you can define properties and methods directly within {}. This
approach is simple and useful for creating a few objects. However, a major limitation is code
repetition—each object will have its own separate copy of the methods, which increases
memory usage. This approach also lacks scalability since maintaining many objects with
similar structures can be cumbersome.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
149

const person1 = {
name: "Alice",
age: 25,
greet() {
console.log(`Hello, my name is ${this.name}.`);
}
};

const person2 = {
name: "Bob",
age: 30,
greet() {
console.log(`Hello, my name is ${this.name}.`);
}
};

person1.greet(); // Output: Hello, my name is Alice.


person2.greet(); // Output: Hello, my name is Bob.

Resulting Table: Objects created using literals

Object Name Age Method (Greet Output)

person1 Alice 25 Hello, my name is Alice.

person2 Bob 30 Hello, my name is Bob.

While this method works, the greet function is duplicated, which wastes memory.

42. Scenario: Efficiently creating multiple objects of the same type

Question: How can you use constructor functions to create multiple objects of the same type
and share methods efficiently?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
150

Answer:
Using constructor functions, you can efficiently create multiple objects of the same type by
defining shared methods on the prototype. This ensures that all instances created with the
constructor share the same method, thus saving memory. Constructors also make the code
reusable and maintainable.

For Example:

function Person(name, age) {


this.name = name;
this.age = age;
}

Person.prototype.greet = function () {
console.log(`Hello, my name is ${this.name}.`);
};

const person1 = new Person("Alice", 25);


const person2 = new Person("Bob", 30);

person1.greet(); // Output: Hello, my name is Alice.


person2.greet(); // Output: Hello, my name is Bob.

Resulting Table:

Instance Name Age Method Reference

person1 Alice 25 Shared (via prototype)

person2 Bob 30 Shared (via prototype)

Both person1 and person2 share the same greet method, ensuring optimal memory usage.

43. Scenario: Inheriting behavior using prototypes

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
151

Question: How can one object inherit properties and methods from another using
prototypes?

Answer:
JavaScript allows objects to inherit behavior through prototypes. When a property or
method is not found in an object, JavaScript looks up the prototype chain to find it. The
Object.create() method allows you to create an object with a specified prototype,
facilitating code reuse and inheritance.

For Example:

const animalPrototype = {
speak() {
console.log(`${this.type} makes a sound.`);
}
};

const dog = Object.create(animalPrototype);


dog.type = "Dog";

dog.speak(); // Output: Dog makes a sound.

In this example, the dog object inherits the speak method from animalPrototype. This
inheritance reduces redundancy by sharing functionality between objects.

44. Scenario: Creating a parent-child class relationship

Question: How would you implement inheritance between two classes using extends and
super?

Answer:
The extends keyword allows a class to inherit from another class, and super() is used to call
the parent class's constructor. This approach promotes code reuse and establishes
hierarchical relationships between objects.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
152

class Animal {
constructor(type) {
this.type = type;
}

sound() {
console.log(`${this.type} makes a sound.`);
}
}

class Dog extends Animal {


constructor(name) {
super("Dog"); // Calls the parent constructor
this.name = name;
}

sound() {
console.log(`${this.name} barks.`);
}
}

const myDog = new Dog("Buddy");


myDog.sound(); // Output: Buddy barks.

Resulting Table: Inheritance Example

Class Property/Method Usage Inherited?

Animal type "Dog" Yes

Dog name "Buddy" No

Dog sound() Overrides Parent No

45. Scenario: Defining static methods

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
153

Question: How can you define a static method in a class, and what is its purpose?

Answer:
Static methods belong to the class itself and are not available on instances. They are typically
used for utility functions that do not depend on instance-specific data.

For Example:

class MathUtils {
static add(a, b) {
return a + b;
}
}

console.log(MathUtils.add(3, 5)); // Output: 8

In this example, the add method can only be accessed via the class (MathUtils) and not
through any instance.

46. Scenario: Using getters and setters for encapsulation

Question: How can you use getters and setters in JavaScript to control access to object
properties?

Answer:
Getters and setters allow you to encapsulate properties and add validation logic when
accessing or setting values. This ensures that properties are managed in a controlled manner.

For Example:

class Person {
constructor(name) {
this._name = name;
}

get name() {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
154

return this._name;
}

set name(newName) {
if (newName) {
this._name = newName;
}
}
}

const person = new Person("Alice");


console.log(person.name); // Output: Alice
person.name = "Bob";
console.log(person.name); // Output: Bob

Here, the private property _name is managed via the name getter and setter.

47. Scenario: Handling this inside object methods

Question: What will be the value of this inside an object’s method, and how does it differ
from arrow functions?

Answer:
In regular methods, this refers to the object that invokes the method. In arrow functions,
this is inherited from the outer context.

For Example:

const person = {
name: "Alice",
greet: function () {
console.log(`Hi, I'm ${this.name}.`);
},
arrowGreet: () => {
console.log(`Hi, I'm ${this.name}.`);
}
};

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
155

person.greet(); // Output: Hi, I'm Alice.


person.arrowGreet(); // Output: Hi, I'm undefined.

48. Scenario: Using call() to change the context of this

Question: How can you use the call() method to invoke a function with a specific this
value?

Answer:
The call() method allows you to explicitly set the this value inside a function. This is useful
when reusing a function across multiple objects.

For Example:

function greet() {
console.log(`Hi, I'm ${this.name}.`);
}

const person = { name: "Alice" };


greet.call(person); // Output: Hi, I'm Alice.

49. Scenario: Preserving this with bind()

Question: How can the bind() method be used to preserve the this value across function
calls?

Answer:
The bind() method returns a new function with this set to the provided value, ensuring the
correct context.

For Example:

const person = {
name: "Alice",

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
156

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

const greet = person.greet.bind(person);


greet(); // Output: Hi, I'm Alice.

50. Scenario: Understanding lexical this in arrow functions

Question: How does the lexical binding of this work in arrow functions, and how is it
different from regular functions?

Answer:
In arrow functions, this is inherited from the outer scope, making it consistent regardless of
where the function is invoked.

For Example:

function Person() {
this.name = "Alice";
this.greet = () => {
console.log(`Hi, I'm ${this.name}.`);
};
}

const person = new Person();


person.greet(); // Output: Hi, I'm Alice.

Arrow functions are particularly useful in cases where the outer this needs to be preserved.

51. Scenario: Using Object.create() for inheritance

Question: How can you use Object.create() to create a new object that inherits from
another object?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
157

Answer:
Object.create() allows you to create a new object with a specified prototype, enabling you
to inherit properties and methods from that prototype. This is a cleaner way to implement
prototypal inheritance without using constructor functions.

For Example:

const animal = {
speak() {
console.log(`${this.type} makes a sound.`);
}
};

const dog = Object.create(animal);


dog.type = "Dog";

dog.speak(); // Output: Dog makes a sound.

Resulting Table:

Object Prototype Property Method Output

dog animal type: Dog Dog makes a sound.

Here, the dog object inherits the speak() method from animal.

52. Scenario: Dynamically adding properties to an object

Question: How can you add properties to an object after it has been created?

Answer:
You can dynamically add properties to an object by assigning values to new keys. This can
be useful when properties need to be added based on conditions or user input.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
158

const car = { brand: "Toyota" };


car.model = "Camry";
car.year = 2020;

console.log(car); // Output: { brand: 'Toyota', model: 'Camry', year: 2020 }

53. Scenario: Using the hasOwnProperty() method

Question: How can you check if an object has a specific property as its own property (and not
inherited)?

Answer:
The hasOwnProperty() method checks whether a property exists directly on the object, not
on its prototype chain.

For Example:

const person = { name: "Alice" };


console.log(person.hasOwnProperty("name")); // Output: true
console.log(person.hasOwnProperty("age")); // Output: false

54. Scenario: Overriding methods in prototypes

Question: How can you override a method inherited from a prototype?

Answer:
You can override a prototype method by redefining it on the specific object or subclass.

For Example:

function Animal() {}
Animal.prototype.speak = function () {
console.log("Animal sound");

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
159

};

const dog = new Animal();


dog.speak = function () {
console.log("Bark");
};

dog.speak(); // Output: Bark

55. Scenario: Preventing changes to an object using Object.freeze()

Question: How does Object.freeze() prevent modifications to an object?

Answer:
Object.freeze() makes an object immutable, preventing new properties from being
added, existing properties from being changed, or properties from being deleted.

For Example:

const person = { name: "Alice" };


Object.freeze(person);

person.name = "Bob"; // This will have no effect


console.log(person.name); // Output: Alice

56. Scenario: Using Object.seal() to prevent adding or deleting properties

Question: What is the difference between Object.freeze() and Object.seal()?

Answer:
Object.seal() prevents new properties from being added or existing properties from being
deleted, but allows modifications to existing properties.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
160

const person = { name: "Alice" };


Object.seal(person);

person.name = "Bob"; // Modification allowed


console.log(person.name); // Output: Bob

delete person.name; // This will have no effect


console.log(person.name); // Output: Bob

57. Scenario: Copying objects using the spread operator

Question: How can you create a shallow copy of an object using the spread operator?

Answer:
The spread operator ({...}) allows you to shallow copy an object. This means only the top-
level properties are copied, and nested objects remain as references.

For Example:

const original = { name: "Alice", address: { city: "New York" } };


const copy = { ...original };

copy.name = "Bob";
copy.address.city = "Los Angeles";

console.log(original.name); // Output: Alice


console.log(original.address.city); // Output: Los Angeles

58. Scenario: Comparing objects by reference

Question: How does JavaScript compare objects, and why might two identical objects not be
equal?

Answer:
JavaScript compares objects by reference, not by value. Two objects with the same structure
and content are considered different if they reference different locations in memory.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
161

For Example:

const obj1 = { name: "Alice" };


const obj2 = { name: "Alice" };

console.log(obj1 === obj2); // Output: false

Here, obj1 and obj2 are different objects, even though their content is identical.

59. Scenario: Using instanceof to check object type

Question: How can you use the instanceof operator to determine if an object is an instance
of a particular class?

Answer:
The instanceof operator checks if an object was created by a particular constructor or
inherits from it.

For Example:

class Animal {}
class Dog extends Animal {}

const myDog = new Dog();

console.log(myDog instanceof Dog); // Output: true


console.log(myDog instanceof Animal); // Output: true

60. Scenario: Chaining methods for clean code

Question: How can you implement method chaining in JavaScript for cleaner code?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
162

Answer:
Method chaining allows multiple methods to be called in a single statement by returning the
object itself from each method.

For Example:

class Calculator {
constructor(value = 0) {
this.value = value;
}

add(num) {
this.value += num;
return this; // Returning the instance to allow chaining
}

subtract(num) {
this.value -= num;
return this;
}

multiply(num) {
this.value *= num;
return this;
}
}

const result = new Calculator(10).add(5).subtract(2).multiply(3).value;


console.log(result); // Output: 39

Resulting Table:

Method Operation Value After Operation

Constructor Initialize 10

add(5) Add 5 15

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
163

subtract(2) Subtract 2 13

multiply(3) Multiply by 3 39

Here, each method returns the current instance (this), enabling the next method to be
called in the same statement.

61. Scenario: Deep copying nested objects

Question: How can you create a deep copy of an object in JavaScript to avoid reference
issues?

Answer:
A deep copy duplicates all levels of an object, ensuring nested objects are also cloned, rather
than sharing references. Using structuredClone() is a simple way to achieve deep copying
without creating custom logic.

For Example:

const original = { name: "Alice", address: { city: "New York" } };


const deepCopy = structuredClone(original);

deepCopy.address.city = "Los Angeles";

console.log(original.address.city); // Output: New York


console.log(deepCopy.address.city); // Output: Los Angeles

Resulting Table:

Object Property Address (City)

original name: Alice New York

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
164

deepCopy name: Alice Los Angeles

This table shows that original and deepCopy are independent, confirming the success of
deep copying.

62. Scenario: Handling circular references in deep copy

Question: How can you create a deep copy of an object that contains circular references?

Answer:
Handling circular references requires tracking visited objects during the deep copy. This
avoids infinite recursion and preserves object relationships.

For Example:

function deepCopy(obj, seen = new WeakMap()) {


if (typeof obj !== "object" || obj === null) return obj;
if (seen.has(obj)) return seen.get(obj);

const copy = Array.isArray(obj) ? [] : {};


seen.set(obj, copy);

for (let key in obj) {


copy[key] = deepCopy(obj[key], seen);
}
return copy;
}

const obj = { name: "Alice" };


obj.self = obj; // Circular reference

const copiedObj = deepCopy(obj);


console.log(copiedObj); // Output: { name: 'Alice', self: [Circular] }

Resulting Table:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
165

Object Property Value

obj name Alice

obj.self Circular [Circular]

copiedObj name Alice

copiedObj.self Circular [Circular]

This shows that both obj and copiedObj maintain circular references correctly.

63. Scenario: Using Proxy to control property access

Question: How can a Proxy be used to add validation to object property access?

Answer:
A Proxy intercepts property operations, allowing validation or customization of behavior
when accessing or modifying properties.

For Example:

const person = { name: "Alice", age: 25 };

const proxy = new Proxy(person, {


set(target, property, value) {
if (property === "age" && typeof value !== "number") {
throw new Error("Age must be a number");
}
target[property] = value;
return true;
}
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
166

proxy.age = 30; // Works fine


console.log(proxy.age); // Output: 30

// proxy.age = "thirty"; // Throws error: Age must be a number

Resulting Table:

Property Value

name Alice

age 30

If invalid data (like "thirty") is set, the Proxy throws an error, ensuring correct types.

64. Scenario: Implementing mixins to combine functionality

Question: How can you use mixins to combine behaviors into a class?

Answer:
Mixins allow multiple behaviors to be added to a class without inheritance, making the class
more flexible and reusable.

For Example:

const CanRun = {
run() {
console.log(`${this.name} is running.`);
}
};

const CanSwim = {
swim() {
console.log(`${this.name} is swimming.`);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
167

}
};

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

Object.assign(Animal.prototype, CanRun, CanSwim);

const dog = new Animal("Dog");


dog.run(); // Output: Dog is running.
dog.swim(); // Output: Dog is swimming.

Resulting Table:

Class Behavior Method Output

Animal CanRun Dog is running.

Animal CanSwim Dog is swimming.

The Animal class gains behaviors from both CanRun and CanSwim.

65. Scenario: Handling asynchronous tasks with class methods

Question: How can you define an asynchronous method in a class to handle API calls?

Answer:
Asynchronous methods allow you to perform tasks like API calls without blocking the main
thread. These methods return a promise, and you can use await to wait for the result.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
168

class DataFetcher {
async fetchData(url) {
const response = await fetch(url);
const data = await response.json();
return data;
}
}

const fetcher = new DataFetcher();


fetcher.fetchData("https://jsonplaceholder.typicode.com/todos/1")
.then(data => console.log(data));

Resulting Table (Fetched Data):

Property Value

id 1

title Sample Todo

completed false

This demonstrates how asynchronous methods enable efficient API interactions.

66. Scenario: Creating immutable objects with Object.freeze()

Question: How can you make an object completely immutable using Object.freeze()?

Answer:
Object.freeze() prevents an object’s properties from being modified, ensuring
immutability. This is useful when you want to ensure data remains constant throughout the
program.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
169

const person = { name: "Alice", age: 25 };


Object.freeze(person);

person.age = 30; // No effect


console.log(person.age); // Output: 25

Resulting Table:

Property Value

name Alice

age 25

Freezing ensures that the person object remains unchanged.

67. Scenario: Using class inheritance to extend functionality

Question: How would you extend a class to add new functionality without modifying the
original class?

Answer:
You can use inheritance to extend functionality by creating a subclass that adds or overrides
methods from the parent class.

For Example:

class Animal {
sound() {
console.log("Animal sound");
}
}

class Dog extends Animal {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
170

sound() {
console.log("Bark");
}
}

const myDog = new Dog();


myDog.sound(); // Output: Bark

Resulting Table:

Class Method Output

Animal sound Animal sound

Dog sound Bark

The Dog class extends the Animal class and overrides the sound() method.

68. Scenario: Comparing shallow and deep copies of objects

Question: What is the difference between shallow and deep copies in JavaScript, and how
can it impact your code?

Answer:
A shallow copy only copies top-level properties, while a deep copy clones all nested objects.
Shallow copies can cause issues if the nested objects are modified.

For Example:

const original = { name: "Alice", address: { city: "New York" } };


const shallowCopy = { ...original };
shallowCopy.address.city = "Los Angeles";

console.log(original.address.city); // Output: Los Angeles

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
171

Resulting Table:

Copy Type Address City

Shallow Copy Los Angeles

Original Los Angeles

In a shallow copy, nested objects are shared by reference.

69. Scenario: Implementing method overloading in JavaScript

Question: How can you implement method overloading in JavaScript, given that it doesn’t
support it natively?

Answer:
You can simulate method overloading by using conditional checks on the number and type
of arguments.

For Example:

class Calculator {
calculate(a, b, operation = "add") {
if (operation === "add") return a + b;
if (operation === "subtract") return a - b;
throw new Error("Unsupported operation");
}
}

const calc = new Calculator();


console.log(calc.calculate(5, 3)); // Output: 8
console.log(calc.calculate(5, 3, "subtract")); // Output: 2

70. Scenario: Implementing private fields in classes

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
172

Question: How can you create private fields in a JavaScript class to prevent direct access?

Answer:
Private fields in JavaScript are declared with a # prefix and are only accessible within the
class.

For Example:

class BankAccount {
#balance;

constructor(initialBalance) {
this.#balance = initialBalance;
}

deposit(amount) {
this.#balance += amount;
}

getBalance() {
return this.#balance;
}
}

const account = new BankAccount(100);


account.deposit(50);
console.log(account.getBalance()); // Output: 150

Resulting Table:

Operation Balance

Initial 100

Deposit 50 150

Private fields ensure that internal data is not directly accessible.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
173

71. Scenario: Preventing object extension with


Object.preventExtensions()

Question: How can you use Object.preventExtensions() to stop new properties from
being added to an object?

Answer:
Object.preventExtensions() prevents adding new properties to an object, but existing
properties can still be modified or deleted. This is useful when you want to limit an object’s
structure.

For Example:

const car = { brand: "Toyota" };


Object.preventExtensions(car);

car.model = "Camry"; // This will not be added


car.brand = "Honda"; // Modification allowed

console.log(car); // Output: { brand: 'Honda' }

Resulting Table:

Property Value

brand Honda

model Not Added

72. Scenario: Making object properties non-configurable using


Object.defineProperty()

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
174

Question: How can you make a property non-configurable using


Object.defineProperty()?

Answer:
Using Object.defineProperty(), you can set a property to be non-configurable, meaning it
cannot be deleted or reconfigured (though its value can still be modified unless made non-
writable).

For Example:

const person = {};


Object.defineProperty(person, "name", {
value: "Alice",
configurable: false,
writable: true
});

person.name = "Bob"; // Modification allowed


// delete person.name; // Throws an error in strict mode

console.log(person.name); // Output: Bob

Resulting Table:

Property Configurable Writable Value

name No Yes Bob

73. Scenario: Using a class method to return another function

Question: How can you use a class method to return another function that preserves the
original this context?

Answer:
You can use arrow functions inside a class method to return another function, ensuring that
this refers to the class instance.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
175

For Example:

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

getGreeter() {
return () => {
console.log(`Hi, I'm ${this.name}.`);
};
}
}

const alice = new Person("Alice");


const greeter = alice.getGreeter();
greeter(); // Output: Hi, I'm Alice.

Resulting Table:

Method Output

getGreeter Hi, I'm Alice.

74. Scenario: Creating a dynamic class method using computed property


names

Question: How can you create dynamic class methods using computed property names?

Answer:
You can use computed property names to define methods dynamically within a class. This
allows methods to be named based on variables or expressions.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
176

const methodName = "sayHello";

class Person {
[methodName]() {
console.log("Hello!");
}
}

const person = new Person();


person.sayHello(); // Output: Hello!

Resulting Table:

Computed Method Name Output

sayHello Hello!

75. Scenario: Using call() to borrow methods from another object

Question: How can you use call() to borrow a method from one object for another?

Answer:
call() allows you to borrow a method from one object and apply it to another by setting
the this context explicitly.

For Example:

const person1 = { name: "Alice" };


const person2 = { name: "Bob" };

function greet() {
console.log(`Hi, I'm ${this.name}.`);
}

greet.call(person1); // Output: Hi, I'm Alice.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
177

greet.call(person2); // Output: Hi, I'm Bob.

Resulting Table:

Object Method Output

person1 Hi, I'm Alice.

person2 Hi, I'm Bob.

76. Scenario: Handling multiple prototypes with the prototype chain

Question: How does the prototype chain work when there are multiple levels of inheritance?

Answer:
In JavaScript, objects inherit properties and methods through a prototype chain. If a
property is not found on the object, JavaScript looks up the chain until it reaches null.

For Example:

const grandparent = { lastName: "Smith" };


const parent = Object.create(grandparent);
parent.firstName = "John";

const child = Object.create(parent);


child.age = 10;

console.log(child.lastName); // Output: Smith

Resulting Table:

Object Properties

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
178

grandparent lastName: Smith

parent firstName: John

child age: 10

The child object inherits properties from both parent and grandparent.

77. Scenario: Using bind() to create a new function with fixed arguments

Question: How can you use bind() to create a new function with some pre-filled
arguments?

Answer:
bind() not only binds the this context but can also pre-fill arguments, creating a new
function with fixed parameters.

For Example:

function multiply(a, b) {
return a * b;
}

const double = multiply.bind(null, 2);


console.log(double(5)); // Output: 10

Resulting Table:

Function Arguments Output

double 5 10

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
179

78. Scenario: Creating multiple instances with class-based inheritance

Question: How can you create multiple instances of a class that inherits properties from a
parent class?

Answer:
Using class-based inheritance, you can create multiple instances that share properties from
the parent class but have their own unique data.

For Example:

class Animal {
constructor(type) {
this.type = type;
}
}

class Dog extends Animal {


constructor(name) {
super("Dog");
this.name = name;
}
}

const dog1 = new Dog("Buddy");


const dog2 = new Dog("Max");

console.log(dog1.name); // Output: Buddy


console.log(dog2.name); // Output: Max

Resulting Table:

Instance Type Name

dog1 Dog Buddy

dog2 Dog Max

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
180

79. Scenario: Comparing two objects using JSON.stringify()

Question: How can you compare two objects to check if they have identical content?

Answer:
You can use JSON.stringify() to compare two objects by converting them to strings. This
method works well for shallow comparisons but might not handle circular references.

For Example:

const obj1 = { name: "Alice", age: 25 };


const obj2 = { name: "Alice", age: 25 };

console.log(JSON.stringify(obj1) === JSON.stringify(obj2)); // Output: true

Resulting Table:

Object Content

obj1 {"name":"Alice","age":25}

obj2 {"name":"Alice","age":25}

80. Scenario: Creating singletons with IIFEs

Question: How can you create a singleton in JavaScript using an IIFE (Immediately Invoked
Function Expression)?

Answer:
A singleton ensures only one instance of an object is created and reused throughout the
application. Using an IIFE, you can create and return a singleton instance.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
181

const Singleton = (function () {


let instance;

function createInstance() {
return { message: "I am the only instance" };
}

return {
getInstance: function () {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();

const instance1 = Singleton.getInstance();


const instance2 = Singleton.getInstance();

console.log(instance1 === instance2); // Output: true

Resulting Table:

Instance Message

instance1 I am the only instance

instance2 I am the only instance

Both instance1 and instance2 are the same, demonstrating the singleton pattern.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
182

Chapter 4: Asynchronous JavaScript

THEORETICAL QUESTIONS
1. What are callbacks in JavaScript?

Answer:
A callback is a function that is passed as an argument to another function and is executed
once the parent function completes. Since JavaScript executes code line by line
(synchronously), callbacks allow you to handle asynchronous events such as making API calls,
reading files, or setting timers without blocking the main thread.

When the asynchronous operation is finished, the callback is invoked to perform follow-up
actions. This way, other code can continue running while waiting for the asynchronous task
to complete.

For Example:

function fetchData(callback) {
console.log('Fetching data...');
setTimeout(() => {
const data = { name: 'Shardul', age: 25 };
callback(data);
}, 2000); // Simulate a delay
}

fetchData((result) => {
console.log('Data received:', result);
});

Here, fetchData simulates an API request using setTimeout. The callback function is passed
as an argument and is invoked after the timeout completes. The result is only logged when
the data is available, demonstrating how callbacks work.

2. What are the drawbacks of using callbacks?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
183

Answer:
The primary drawback of callbacks is that they can lead to "callback hell" or "pyramid of
doom"—a situation where multiple callbacks are deeply nested, making the code difficult to
read, understand, and maintain. Each nested callback also introduces more complexity,
making error handling more challenging. As the number of nested callbacks increases, the
code becomes harder to manage, often causing bugs.

Another issue is that managing multiple asynchronous operations in sequence using


callbacks becomes tricky. Promises and async/await were introduced to address these
challenges.

For Example:

getUserData((user) => {
getOrders(user.id, (orders) => {
getOrderDetails(orders[0], (details) => {
console.log(details);
});
});
});

In this example, multiple asynchronous operations are nested inside one another, making
the code hard to maintain.

3. What is a promise in JavaScript?

Answer:
A promise is a JavaScript object used to handle asynchronous operations. It represents a
value that may be available in the future. A promise has three states:

● Pending: The operation has not completed yet.


● Fulfilled: The operation completed successfully.
● Rejected: The operation failed.

Promises simplify handling asynchronous tasks and avoid the problem of callback hell by
allowing functions to return a promise, which can be handled using .then() for success and
.catch() for errors.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
184

For Example:

let fetchData = new Promise((resolve, reject) => {


setTimeout(() => {
resolve('Data fetched successfully');
}, 1000);
});

fetchData
.then((data) => console.log(data))
.catch((error) => console.error(error));

In this example, fetchData returns a promise. Once the asynchronous operation is complete,
it resolves, and the result is handled in the .then() block.

4. How do you handle errors in promises?

Answer:
Error handling in promises is done using the .catch() method. If any error occurs during the
promise execution, it gets propagated to the nearest .catch() block. Proper error handling
is crucial to ensure that exceptions don’t break the application flow and that users receive
appropriate feedback.

For Example:

let fetchData = new Promise((resolve, reject) => {


let success = false;
if (success) {
resolve('Data fetched');
} else {
reject('Error: Unable to fetch data');
}
});

fetchData
.then((data) => console.log(data))

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
185

.catch((error) => console.error(error));

In this case, if the operation fails, the reject function is called, and the error message is
handled by the .catch() method.

5. What is promise chaining?

Answer:
Promise chaining allows you to perform a series of asynchronous operations in a sequence by
linking multiple .then() calls. Each .then() receives the result of the previous promise and
returns a new promise, enabling further operations to be chained. This structure makes the
code more readable and easier to manage compared to deeply nested callbacks.

For Example:

new Promise((resolve) => {


resolve(10);
})
.then((result) => result * 2) // 10 * 2 = 20
.then((result) => result + 5) // 20 + 5 = 25
.then((result) => console.log(result)); // Output: 25

Here, each .then() modifies the result and passes it to the next step, demonstrating a clear
flow of logic.

6. What are async functions in JavaScript?

Answer:
async functions are a way to write cleaner and more readable asynchronous code. An async
function always returns a promise, even if the function appears to return a value. The await
keyword can only be used inside async functions to pause the execution until a promise
resolves, making the code look more like synchronous code. This improves readability and
avoids callback hell or complex promise chains.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
186

For Example:

async function fetchData() {


let response = await new Promise((resolve) =>
setTimeout(() => resolve('Data fetched'), 1000)
);
console.log(response);
}

fetchData();

In this example, the await keyword ensures that the function waits for the promise to resolve
before logging the result.

7. How do you handle errors with async/await?

Answer:
Errors in async functions are handled using try-catch blocks. If any error occurs during the
asynchronous operation, the control jumps to the catch block. This makes it easier to handle
errors compared to using .catch() with promises, especially in complex asynchronous flows.

For Example:

async function fetchData() {


try {
let response = await new Promise((_, reject) =>
setTimeout(() => reject('Error fetching data'), 1000)
);
console.log(response);
} catch (error) {
console.error(error);
}
}

fetchData();

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
187

Here, if the promise is rejected, the error is caught by the catch block and logged.

8. What is the event loop in JavaScript?

Answer:
The event loop is a mechanism in JavaScript that manages the execution of asynchronous
code. JavaScript is single-threaded, meaning only one operation can run at a time. The event
loop allows asynchronous tasks (like callbacks, promises, and timers) to be executed after the
synchronous code finishes. It ensures that the call stack is clear before processing tasks from
the event queue.

For Example:

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

Here, setTimeout schedules the task to run later. The synchronous code runs first, followed
by the callback from the event loop.

9. What is the difference between microtasks and macrotasks?

Answer:
Microtasks are tasks that need to be executed as soon as the current synchronous code
completes. Examples include promise callbacks and MutationObserver callbacks.
Macrotasks are scheduled tasks such as setTimeout, setInterval, and DOM events.

Microtasks have higher priority than macrotasks and are executed before any macrotasks,
even if a macrotask was scheduled first.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
188

console.log('Start');

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

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

console.log('End');

Even though the macrotask was scheduled with setTimeout, the microtask runs first.

10. Why is JavaScript called single-threaded?

Answer:
JavaScript is called single-threaded because it executes code one line at a time in a single
thread. This means only one operation can run on the main thread at any given moment.
Even though JavaScript can handle asynchronous operations, those operations do not block
the main thread but are executed via callbacks, promises, or the event loop.

For Example:

console.log('Task 1');
setTimeout(() => console.log('Task 2'), 0);
console.log('Task 3');

The asynchronous task (Task 2) is added to the event queue and executed after the
synchronous code completes

11. What is the purpose of the setTimeout function in JavaScript?

Answer:
The setTimeout function is used to schedule a task to run after a specific delay (in
milliseconds). It allows developers to execute code asynchronously, meaning the scheduled

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
189

task will run only after all current synchronous operations are finished. The function takes two
parameters:

1. A callback function – The code to be executed after the delay.


2. A delay – The time in milliseconds before the callback function is executed.

Using setTimeout is useful for creating animations, handling delayed events, or simulating
network operations without blocking the main thread.

For Example:

console.log('Start');

setTimeout(() => {
console.log('Executed after 2 seconds');
}, 2000);

console.log('End');

Here, the setTimeout schedules the callback function to run after 2 seconds, while the
synchronous code continues to run without waiting.

12. How does setInterval differ from setTimeout?

Answer:
setInterval is similar to setTimeout, but instead of executing a function once after a delay,
it repeatedly runs the callback function at the specified interval. This makes setInterval
useful for tasks that need to occur periodically, like updating a clock or checking for new
data. The interval will continue until it is manually stopped using clearInterval.

For Example:

let count = 0;
const intervalId = setInterval(() => {
console.log(`Count: ${++count}`);
if (count === 5) clearInterval(intervalId); // Stop after 5 counts
}, 1000);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
190

Here, the function runs every second and prints the count. It stops when the count reaches 5,
using clearInterval.

13. What is the purpose of clearTimeout and clearInterval?

Answer:
clearTimeout and clearInterval are used to stop a scheduled setTimeout or setInterval
task, respectively.

● clearTimeout: Prevents a function from running if the specified delay hasn’t finished
yet.
● clearInterval: Stops the repeated execution of a function initiated by setInterval.

For Example:

const timeoutId = setTimeout(() => {


console.log('This will not run');
}, 3000);

clearTimeout(timeoutId); // Stops the timeout before it runs

Here, the timeout is canceled using clearTimeout, so the message will never be logged.

14. What are synchronous and asynchronous code?

Answer:

● Synchronous code: Executes line by line, blocking the next operation until the current
one completes. This can cause delays if a task takes time (like a network request).
● Asynchronous code: Allows other operations to run while waiting for a long task to
complete, improving performance and responsiveness. JavaScript achieves this using
callbacks, promises, and async/await.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
191

console.log('Start');

setTimeout(() => console.log('Async Operation'), 2000);

console.log('End');

Here, setTimeout is asynchronous, allowing the rest of the code to execute first.

15. What is the difference between .then() and .catch() in promises?

Answer:

● .then() handles the success of a promise and receives the resolved value. It can be
chained to perform multiple operations sequentially.
● .catch() handles the rejection or error from a promise. It ensures that even if
something goes wrong, the error is caught and handled gracefully.

For Example:

new Promise((resolve, reject) => {


const success = false;
success ? resolve('Success') : reject('Failure');
})
.then((result) => console.log(result)) // If success
.catch((error) => console.error(error)); // If failure

If the promise is rejected, .catch() will log the error.

16. Can you return a promise from .then()?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
192

Answer:
Yes, returning a promise from .then() allows you to chain multiple asynchronous
operations. Each .then() waits for the previous promise to resolve before running. This helps
in writing sequential code without callback hell.

For Example:

new Promise((resolve) => resolve(2))


.then((num) => new Promise((resolve) => resolve(num * 2)))
.then((result) => console.log(result)); // Output: 4

Here, the first .then() returns a promise, and the next .then() logs the result.

17. How does JavaScript handle multiple asynchronous operations?

Answer:
JavaScript uses the event loop and task queues to manage multiple asynchronous
operations. When an asynchronous operation (like a setTimeout or a promise) is completed,
its callback is added to the task queue. The event loop checks the task queue and moves
tasks to the call stack when the stack is empty, ensuring non-blocking execution.

● Microtasks (like promises) have higher priority than macrotasks (like setTimeout).

18. What happens if you don’t handle a promise rejection?

Answer:
If a promise is rejected and no .catch() block is provided, JavaScript raises an
UnhandledPromiseRejectionWarning. In production, unhandled rejections can crash
applications or cause unpredictable behavior, so it is important to handle all rejections
properly.

For Example:

new Promise((_, reject) => reject('Error'))

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
193

.then((result) => console.log(result)); // No .catch()

This will result in an UnhandledPromiseRejection.

19. Can you convert a callback-based function to use promises?

Answer:
Yes, callback-based functions can be converted to promise-based ones using the Promise
constructor. This process is called promisification. Promises offer better error handling and
cleaner code structure compared to callbacks.

For Example:

function fetchData(callback) {
setTimeout(() => callback('Data fetched'), 1000);
}

function fetchDataPromise() {
return new Promise((resolve) => {
fetchData(resolve);
});
}

fetchDataPromise().then((data) => console.log(data));

In this example, the callback-based function fetchData is converted to a promise-based


version.

20. What are the benefits of using async/await over promises?

Answer:
The async/await syntax makes asynchronous code look and behave more like synchronous
code, improving readability and maintainability. Instead of chaining .then() methods, you

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
194

can write linear code using await. This also simplifies error handling by using try-catch
blocks.

For Example:

async function fetchData() {


try {
let data = await new Promise((resolve) => setTimeout(() => resolve('Data'),
1000));
console.log(data);
} catch (error) {
console.error(error);
}
}

fetchData();

Here, the await keyword pauses the function until the promise resolves, avoiding the need
for .then() and making the code more readable.

21. How does the event loop prioritize microtasks and macrotasks?

Answer:
The event loop manages the execution of tasks in JavaScript, ensuring that asynchronous
code executes properly. Tasks are divided into microtasks and macrotasks:

● Microtasks (e.g., promise callbacks, MutationObserver) run after the currently


executing script finishes and before the event loop moves to the next macrotask.
● Macrotasks (e.g., setTimeout, setInterval, and I/O events) are executed at the next
iteration of the event loop, after all microtasks are completed.

The event loop ensures microtasks are processed immediately to avoid delays, which is why
promises often execute sooner than setTimeout callbacks.

For Example:

console.log('Start');

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
195

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

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

console.log('End');

Here, although both tasks are scheduled to execute immediately, the microtask (promise)
runs before the macrotask (setTimeout).

22. Can you explain the difference between process.nextTick() and


setImmediate()?

Answer:
Both process.nextTick() and setImmediate() are used for scheduling tasks in Node.js, but
they differ in timing:

● process.nextTick(): Schedules a callback to run before the next event loop


iteration. It has higher priority and runs after the current operation but before I/O
tasks.
● setImmediate(): Schedules a callback to run on the next iteration of the event loop,
after I/O events.

For Example:

console.log('Start');

process.nextTick(() => console.log('Next Tick')); // Executes first

setImmediate(() => console.log('Set Immediate')); // Executes after I/O

console.log('End');

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
196

Here, the process.nextTick() callback runs before setImmediate() due to its higher
priority.

23. How does JavaScript handle stack overflow errors?

Answer:
A stack overflow occurs when too many function calls are made without returning,
exceeding the call stack's capacity. This can happen in cases of infinite recursion or deep
function nesting. JavaScript throws a RangeError when the call stack limit is reached.

For Example:

function recurse() {
recurse(); // Infinite recursion without a base case
}

recurse(); // Throws RangeError: Maximum call stack size exceeded

To prevent stack overflow, ensure recursive functions have a base case to terminate the
recursion.

24. What are the differences between Promise.all, Promise.race,


Promise.any, and Promise.allSettled?

Answer:
These promise methods help manage multiple asynchronous operations:

● Promise.all: Resolves when all promises are fulfilled; rejects if any promise is
rejected.
● Promise.race: Resolves or rejects as soon as the first promise settles.
● Promise.any: Resolves if at least one promise resolves; rejects only if all promises
reject.
● Promise.allSettled: Returns an array with the results of all promises, regardless of
whether they resolve or reject.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
197

const p1 = Promise.resolve(1);
const p2 = new Promise((_, reject) => setTimeout(() => reject('Error'), 100));
const p3 = Promise.resolve(3);

Promise.all([p1, p2, p3])


.then((results) => console.log(results))
.catch((error) => console.error(error)); // Logs: Error

Here, Promise.all rejects as soon as one promise fails.

25. What is the difference between synchronous and asynchronous


iterators?

Answer:

● Synchronous Iterators: Iterate over data that is available immediately using a


for...of loop.
● Asynchronous Iterators: Work with data that arrives asynchronously, such as
streaming data. They use the for await...of loop.

For Example:

async function* asyncGenerator() {


yield 'First';
yield new Promise((resolve) => setTimeout(() => resolve('Second'), 1000));
}

(async () => {
for await (const value of asyncGenerator()) {
console.log(value);
}
})();

This example shows how asynchronous iterators handle delayed data.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
198

26. How do you cancel an ongoing promise?

Answer:
JavaScript doesn’t natively support promise cancellation, but you can achieve it using
AbortController. This allows you to abort ongoing operations, like a network request.

For Example:

const controller = new AbortController();


const signal = controller.signal;

const fetchData = new Promise((resolve, reject) => {


signal.addEventListener('abort', () => reject('Aborted'));
setTimeout(() => resolve('Data fetched'), 5000);
});

setTimeout(() => controller.abort(), 2000); // Abort after 2 seconds

fetchData
.then((data) => console.log(data))
.catch((error) => console.error(error)); // Logs: Aborted

Here, the promise is canceled before completion using AbortController.

27. What is backpressure in JavaScript streams?

Answer:
Backpressure occurs when a readable stream produces data faster than a writable stream
can consume. This leads to a buildup of data in memory, which can degrade performance or
cause memory leaks. To handle backpressure, streams can slow down or pause the flow of
data until the writable stream is ready to consume more.

28. What is the purpose of queueMicrotask()?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
199

Answer:
queueMicrotask() is used to schedule a microtask, ensuring it runs after the current
synchronous code completes but before the next event loop iteration. It behaves similarly to
the .then() method in promises.

For Example:

console.log('Start');

queueMicrotask(() => console.log('Microtask'));


console.log('End');

Here, the microtask runs after the synchronous code.

29. How do you handle promises sequentially?

Answer:
To execute promises in sequence, avoid Promise.all (which runs promises in parallel).
Instead, use async/await or chain .then() methods to ensure one promise resolves before
starting the next.

For Example:

async function sequentialTasks() {


const result1 = await new Promise((resolve) => resolve('Task 1'));
console.log(result1);

const result2 = await new Promise((resolve) => resolve('Task 2'));


console.log(result2);
}

sequentialTasks();

Here, each task waits for the previous one to complete.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
200

30. How does JavaScript’s single-threaded nature affect concurrency?

Answer:
JavaScript is single-threaded, meaning it can only execute one task at a time on the main
thread. However, it achieves concurrency through non-blocking I/O operations and the
event loop. Asynchronous operations (e.g., API calls) run in the background, and their results
are handled later via callbacks or promises, ensuring the main thread remains responsive.

This allows JavaScript to manage multiple tasks efficiently without needing multiple threads,
avoiding common concurrency issues like race conditions.

31. How does Promise.all behave with empty arrays or non-promise


values?

Answer:
Promise.all is designed to handle multiple promises in parallel and returns an array of
resolved values if all the promises succeed. If any promise fails, it rejects immediately. It also
behaves predictably with non-promise values and empty arrays:

● If non-promise values are provided, Promise.all treats them as already resolved.


● If the array is empty, it resolves immediately with an empty array ([]).

For Example:

Promise.all([1, Promise.resolve(2), 3])


.then((results) => console.log(results)) // Logs: [1, 2, 3]
.catch((error) => console.error(error));

32. What is a "race condition," and how can it affect JavaScript


applications?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
201

Answer:
A race condition happens when multiple asynchronous operations are executed
simultaneously, but the program’s correctness depends on their order. If they complete in an
unexpected order, the outcome may be wrong or inconsistent, causing bugs. This problem
occurs often when accessing shared data without control over the sequence of operations.

For Example:

let data = {};

Promise.resolve('User 1').then((user) => {


data.user = user;
});

Promise.resolve('Settings').then((settings) => {
data.settings = settings;
});

console.log(data); // Output could be: {} or { user: 'User 1' }

In this example, the timing of the two promises is unpredictable, which may result in an
incomplete state when console.log(data) runs. Using Promise.all() or async/await
ensures the operations complete in a controlled manner.

33. How does the Promise.race method handle rejections?

Answer:
Promise.race returns the result of the first settled promise, regardless of whether it resolves
or rejects. If the first promise to settle is rejected, Promise.race rejects immediately with that
reason, even if other promises could still resolve later.

For Example:

const p1 = new Promise((_, reject) => setTimeout(() => reject('Error'), 100));


const p2 = new Promise((resolve) => setTimeout(() => resolve('Success'), 200));

Promise.race([p1, p2])

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
202

.then((result) => console.log(result))


.catch((error) => console.error(error)); // Logs: Error

Here, p1 rejects first, so Promise.race rejects with 'Error', ignoring p2.

34. What are "zombie promises," and how do you avoid them?

Answer:
A zombie promise refers to a promise that continues to run even though its result is no
longer needed. This can happen if the promise was initiated but the user navigates away
from the page, or the operation is no longer relevant. Zombie promises consume resources
and may cause unintended behavior.

Solution: Use AbortController to cancel the promise when it is no longer needed.

For Example:

const controller = new AbortController();


const signal = controller.signal;

const fetchData = new Promise((resolve, reject) => {


signal.addEventListener('abort', () => reject('Aborted'));
setTimeout(() => resolve('Fetched Data'), 3000);
});

controller.abort(); // Cancel the promise immediately

fetchData
.then((data) => console.log(data))
.catch((error) => console.error(error)); // Logs: Aborted

Here, the promise is canceled before completion using AbortController, preventing zombie
promises.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
203

35. What is the difference between Promise.resolve() and


Promise.reject()?

Answer:

● Promise.resolve(): Creates a promise that resolves immediately with the provided


value. It’s useful when you want a promise that resolves right away, for testing or
synchronous logic.
● Promise.reject(): Creates a promise that is immediately rejected. It’s helpful to
simulate errors during testing or error handling.

For Example:

Promise.resolve('Resolved Value').then((result) => console.log(result)); // Logs:


Resolved Value

Promise.reject('Error').catch((error) => console.error(error)); // Logs: Error

36. How do you use Promise.allSettled to handle both resolved and


rejected promises?

Answer:
Promise.allSettled waits for all promises to settle (either resolve or reject) and returns an
array of objects representing the outcomes. It provides both successful results and errors,
ensuring that all promises are accounted for, even if some fail.

For Example:

const promises = [
Promise.resolve('Task 1'),
Promise.reject('Task 2 failed'),
Promise.resolve('Task 3'),
];

Promise.allSettled(promises).then((results) => console.log(results));

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
204

This method ensures all results are logged, regardless of success or failure.

37. What is the purpose of Symbol.asyncIterator in JavaScript?

Answer:
Symbol.asyncIterator allows an object to define how it will be asynchronously iterated
using for await...of. It’s useful when working with data streams or APIs that provide data
over time.

For Example:

const asyncIterable = {
async *[Symbol.asyncIterator]() {
yield 'First';
yield new Promise((resolve) => setTimeout(() => resolve('Second'), 1000));
},
};

(async () => {
for await (const value of asyncIterable) {
console.log(value);
}
})();

This example shows how asynchronous iteration is implemented using


Symbol.asyncIterator.

38. How do you handle errors in multiple asynchronous operations using


try-catch?

Answer:
Using try-catch with async/await ensures that errors in asynchronous operations are

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
205

caught and handled properly. Each asynchronous operation can be wrapped in its own try-
catch block, or a single block can manage multiple operations.

For Example:

async function fetchData() {


try {
const user = await Promise.resolve('User');
console.log(user);

const settings = await Promise.reject('Settings Error'); // This will throw


console.log(settings); // Won't run due to the error
} catch (error) {
console.error('Error:', error);
}
}

fetchData(); // Logs: Error: Settings Error

Here, the error in the second promise is caught by the catch block.

39. What is the purpose of await Promise.all()?

Answer:
Using await with Promise.all() allows multiple asynchronous operations to run in parallel
and waits for all of them to complete before proceeding. This improves performance when
tasks are independent.

For Example:

async function fetchAllData() {


const [user, posts] = await Promise.all([
Promise.resolve('User Data'),
new Promise((resolve) => setTimeout(() => resolve('Posts Data'), 1000)),
]);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
206

console.log(user, posts); // Logs: User Data Posts Data


}

fetchAllData();

Here, both promises are executed simultaneously, making the code more efficient.

40. How do you implement retry logic with promises in JavaScript?

Answer:
Retry logic is helpful when dealing with unreliable operations like API calls. You can
implement retries using recursion or loops. If an operation fails, it retries a specified number
of times before giving up.

For Example:

async function retryOperation(operation, retries) {


try {
const result = await operation();
console.log('Success:', result);
} catch (error) {
if (retries > 0) {
console.log(`Retrying... (${retries} attempts left)`);
await retryOperation(operation, retries - 1); // Recursive retry
} else {
console.error('Failed:', error);
}
}
}

const fetchData = () =>


new Promise((resolve, reject) =>
Math.random() > 0.5 ? resolve('Fetched Data') : reject('Error')
);

retryOperation(fetchData, 3);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
207

This code retries the operation up to three times if it fails.

SCENARIO QUESTIONS
41.

Scenario:
You want to display a loading message while fetching data from an API. Once the data is
fetched, the loading message should disappear, and the data should be displayed.

Question:
How can you achieve this using a callback function to handle asynchronous behavior?

Answer:
A callback function helps execute code after an asynchronous operation completes. In this
scenario, the fetchData function simulates a network request using setTimeout. The loading
message is displayed immediately when the function is called, and once the data is fetched,
the callback function is triggered to display the data and remove the loading message. This is
a typical example of how callbacks help manage sequential actions for asynchronous
operations.

For Example:

function fetchData(callback) {
console.log('Loading...');
setTimeout(() => {
const data = { user: 'Shardul', age: 25 };
callback(data);
}, 2000);
}

function displayData(data) {
console.log('Data:', data);
console.log('Loading complete!');
}

fetchData(displayData);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
208

Output:

Loading...
Data: { user: 'Shardul', age: 25 }
Loading complete!

Here, the fetchData function calls the displayData function only after the data is available.

42.

Scenario:
You are building an online store and need to ensure that product details are loaded before
processing the user’s order.

Question:
How can promises be used to fetch product details and handle both success and error
scenarios?

Answer:
A promise is a more organized way to manage asynchronous code. Instead of passing
callbacks, a promise returns a future result, which can be handled using .then() for success
and .catch() for failure. In this scenario, the fetchProduct function returns a promise that
simulates fetching product data. If the product is successfully fetched, the result is logged. If
the operation fails, the error is handled in the .catch() block.

For Example:

function fetchProduct(productId) {
return new Promise((resolve, reject) => {
const success = true; // Simulate success or failure
setTimeout(() => {
if (success) resolve({ id: productId, name: 'Laptop' });
else reject('Failed to fetch product details');
}, 1000);
});
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
209

fetchProduct(101)
.then((product) => console.log('Product:', product))
.catch((error) => console.error('Error:', error));

Here, the product details are only logged if the promise resolves successfully, ensuring the
order is processed with the correct data.

43.

Scenario:
You need to fetch user details from an API and then fetch the user’s orders based on the
retrieved user ID.

Question:
How can you chain promises to perform these tasks sequentially?

Answer:
Promise chaining ensures that tasks are performed in sequence by returning one promise
from a .then() method and passing its result to the next. In this scenario, the fetchUser
promise resolves with user data, and the fetchOrders promise resolves with orders using the
user’s ID. Each .then() step depends on the result of the previous one.

For Example:

function fetchUser() {
return new Promise((resolve) => {
setTimeout(() => resolve({ id: 1, name: 'Shardul' }), 1000);
});
}

function fetchOrders(userId) {
return new Promise((resolve) => {
setTimeout(() => resolve(['Order 1', 'Order 2']), 1000);
});
}

fetchUser()

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
210

.then((user) => {
console.log('User:', user);
return fetchOrders(user.id);
})
.then((orders) => console.log('Orders:', orders))
.catch((error) => console.error('Error:', error));

Here, the orders are fetched only after the user details are retrieved, maintaining the correct
sequence.

44.

Scenario:
You want to fetch user data from an API using modern async/await syntax to improve code
readability.

Question:
How can you use async/await to fetch and display user data?

Answer:
The async/await syntax simplifies asynchronous code by making it look like synchronous
code. async functions return a promise, and the await keyword pauses the execution until
the promise resolves. This improves code readability and makes it easier to handle errors with
try-catch.

For Example:

async function fetchUser() {


try {
const user = await new Promise((resolve) =>
setTimeout(() => resolve({ id: 1, name: 'Shardul' }), 1000)
);
console.log('User:', user);
} catch (error) {
console.error('Error:', error);
}
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
211

fetchUser();

Here, await ensures that the data is logged only after the promise resolves.

45.

Scenario:
Your web page must display a message if an API call fails to load data within 3 seconds.

Question:
How can you implement a timeout using promises to handle this situation?

Answer:
Using Promise.race, you can race two promises: one representing the API call and another
representing a timeout. If the timeout finishes first, it rejects with a timeout error.

For Example:

function fetchData() {
return new Promise((resolve) => setTimeout(() => resolve('Data Loaded'), 5000));
}

function timeout(ms) {
return new Promise((_, reject) => setTimeout(() => reject('Timeout!'), ms));
}

Promise.race([fetchData(), timeout(3000)])
.then((data) => console.log(data))
.catch((error) => console.error(error));

If the fetch takes longer than 3 seconds, the timeout message is displayed.

46.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
212

Scenario:
You have multiple API calls to make, and you want all of them to execute in parallel.

Question:
How can you use Promise.all to run multiple API calls in parallel and wait for all of them to
complete?

Answer:
Promise.all allows multiple promises to run in parallel. It waits for all promises to resolve
and returns their results in an array. If any promise rejects, Promise.all returns the rejection.

For Example:

const apiCall1 = new Promise((resolve) => setTimeout(() => resolve('API 1'),


1000));
const apiCall2 = new Promise((resolve) => setTimeout(() => resolve('API 2'),
2000));

Promise.all([apiCall1, apiCall2])
.then((results) => console.log('Results:', results))
.catch((error) => console.error('Error:', error));

Both API calls execute in parallel, improving performance.

47.

Scenario:
You need to cancel a fetch request if the user navigates away from the page.

Question:
How can you implement cancellation using AbortController?

Answer:
AbortController allows you to cancel ongoing fetch requests. The signal from
AbortController is passed to the fetch request, enabling cancellation.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
213

const controller = new AbortController();


const signal = controller.signal;

fetch('https://jsonplaceholder.typicode.com/posts', { signal })
.then((response) => response.json())
.then((data) => console.log('Data:', data))
.catch((error) => console.error('Fetch aborted:', error));

setTimeout(() => controller.abort(), 1000); // Cancel after 1 second

If the fetch isn’t completed in time, it’s canceled.

48.

Scenario:
You need to ensure that even if one API call fails, the other results are still processed.

Question:
How can you use Promise.allSettled to handle both resolved and rejected promises?

Answer:
Promise.allSettled waits for all promises to settle and returns their results, whether they
resolve or reject.

For Example:

const p1 = Promise.resolve('API 1 Success');


const p2 = Promise.reject('API 2 Failed');
const p3 = Promise.resolve('API 3 Success');

Promise.allSettled([p1, p2, p3]).then((results) => console.log(results));

All results are logged, even if some promises fail.

49.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
214

Scenario:
You want to retry an API call up to 3 times if it fails.

Question:
How can you implement retry logic using async/await?

Answer:
Retry logic ensures that operations are retried until they succeed or reach a limit.

For Example:

async function fetchWithRetry(url, retries) {


for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url);
const data = await response.json();
return data;
} catch (error) {
console.error(`Attempt ${i + 1} failed`);
}
}
throw new Error('All attempts failed');
}

fetchWithRetry('https://jsonplaceholder.typicode.com/posts', 3)
.then((data) => console.log('Data:', data))
.catch((error) => console.error('Error:', error));

The fetch is retried up to 3 times if it fails.

50.

Scenario:
You need to delay the execution of a function by 2 seconds.

Question:
How can you create a reusable delay function using promises?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
215

Answer:
A delay function can be created using promises to pause execution for a specified time.

For Example:

function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

async function executeTask() {


console.log('Task starting...');
await delay(2000);
console.log('Task completed after 2 seconds');
}

executeTask();

Here, the task is paused for 2 seconds before resuming.

51.

Scenario:
You need to log the results of multiple promises one by one, in the order they resolve, even
though they are executed in parallel.

Question:
How can you ensure promises run in parallel but log their results sequentially?

Answer:
When multiple promises are executed in parallel using Promise.all, their results are
collected in the same order as the promises were defined, even if they resolve at different
times. Using a for...of loop ensures the results are logged sequentially. This pattern allows
the code to take advantage of parallel execution while maintaining order in the output.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
216

const promises = [
new Promise((resolve) => setTimeout(() => resolve('Result 1'), 2000)),
new Promise((resolve) => setTimeout(() => resolve('Result 2'), 1000)),
new Promise((resolve) => setTimeout(() => resolve('Result 3'), 3000)),
];

async function logResultsInOrder() {


const results = await Promise.all(promises); // Execute in parallel
for (const result of results) {
console.log(result); // Log sequentially
}
}

logResultsInOrder();

In this example, the promises run in parallel, but the results are logged in order: Result 1,
Result 2, and Result 3.

52.

Scenario:
You want to execute multiple asynchronous functions one after the other, ensuring each one
finishes before the next starts.

Question:
How can you execute asynchronous functions sequentially using async/await?

Answer:
Using async/await ensures that each asynchronous function completes before the next one
starts. Even though asynchronous functions usually run in parallel, using await inside a loop
forces them to execute sequentially.

For Example:

async function task1() {


console.log('Executing Task 1');
return new Promise((resolve) => setTimeout(resolve, 1000));
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
217

async function task2() {


console.log('Executing Task 2');
return new Promise((resolve) => setTimeout(resolve, 2000));
}

async function runTasks() {


await task1(); // Wait for Task 1 to complete
await task2(); // Wait for Task 2 to complete
console.log('All tasks completed');
}

runTasks();

Here, the execution is strictly sequential: Task 1 completes before Task 2 starts.

53.

Scenario:
You want to catch and handle errors occurring in a specific part of an async function while
allowing other operations to continue.

Question:
How can you handle errors in specific sections of an async function using try-catch?

Answer:
Wrapping different sections of an async function in individual try-catch blocks ensures
errors are handled separately. This allows the function to continue executing even if one
section fails, improving fault tolerance.

For Example:

async function performTasks() {


try {
const result1 = await Promise.resolve('Task 1 Success');
console.log(result1);
} catch (error) {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
218

console.error('Error in Task 1:', error);


}

try {
const result2 = await Promise.reject('Task 2 Failed');
console.log(result2); // Won't execute
} catch (error) {
console.error('Error in Task 2:', error);
}
}

performTasks();

Here, if Task 2 fails, the error is handled, and the function continues without interruption.

54.

Scenario:
You need to ensure that a function is executed only after a user stops typing for a certain
period.

Question:
How can you implement a debounce function in JavaScript?

Answer:
A debounce function delays the execution of a function until after a specified period of
inactivity. This is useful in scenarios like search input fields, where you want to wait until the
user stops typing before sending a request.

For Example:

function debounce(func, delay) {


let timeout;
return function (...args) {
clearTimeout(timeout); // Reset the timeout on each call
timeout = setTimeout(() => func(...args), delay);
};

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
219

const log = debounce(() => console.log('Button clicked'), 1000);

log(); // Call multiple times, only the last one executes after 1 second
log();
log();

The debounce ensures only the final call executes after a 1-second delay.

55.

Scenario:
You want to limit how often a function can run during continuous events like scrolling.

Question:
How can you implement a throttle function in JavaScript?

Answer:
A throttle function ensures a function executes only once within a specified time period,
even if called multiple times. It’s useful for optimizing heavy operations triggered by
continuous events like scrolls or mouse movements.

For Example:

function throttle(func, limit) {


let lastCall = 0;
return function (...args) {
const now = Date.now();
if (now - lastCall >= limit) {
lastCall = now;
func(...args);
}
};
}

const log = throttle(() => console.log('Function executed'), 1000);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
220

log(); // Executes immediately


setTimeout(log, 500); // Ignored
setTimeout(log, 1500); // Executes after 1.5 seconds

This ensures the function runs at most once every 1 second, even if called frequently.

56.

Scenario:
You need to implement a countdown timer that decreases every second until it reaches zero.

Question:
How can you create a countdown timer using setInterval?

Answer:
setInterval repeatedly executes a function at regular intervals. In this scenario, the timer
decreases every second and stops when it reaches zero using clearInterval.

For Example:

function countdown(seconds) {
const interval = setInterval(() => {
console.log(seconds);
seconds--;
if (seconds < 0) {
clearInterval(interval); // Stop the timer
console.log('Countdown complete!');
}
}, 1000);
}

countdown(5);

The countdown runs from 5 to 0, logging each second.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
221

57.

Scenario:
You want to run code only after the entire DOM content has loaded.

Question:
How can you detect when the DOM is fully loaded in JavaScript?

Answer:
The DOMContentLoaded event triggers when the HTML document is completely loaded and
parsed, without waiting for stylesheets, images, or other resources to load.

For Example:

document.addEventListener('DOMContentLoaded', () => {
console.log('DOM fully loaded and parsed');
});

This ensures your script runs only after the DOM is ready.

58.

Scenario:
You need to execute a function only once, even if it’s called multiple times.

Question:
How can you implement a function that executes only once using JavaScript?

Answer:
You can use closures to create a function that runs only once. After the first execution, the
function remembers that it has already run and ignores subsequent calls.

For Example:

function once(func) {
let executed = false;
return function (...args) {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
222

if (!executed) {
executed = true;
func(...args);
}
};
}

const logOnce = once(() => console.log('Executed!'));

logOnce(); // Executed!
logOnce(); // Ignored

This ensures the function executes only once.

59.

Scenario:
You want to delay each iteration in a loop by a specific amount of time.

Question:
How can you create a delay between loop iterations using async/await?

Answer:
Using async/await with a delay function allows you to pause between iterations in a loop.

For Example:

function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

async function delayedLoop() {


for (let i = 1; i <= 3; i++) {
console.log(`Iteration ${i}`);
await delay(1000); // 1-second delay
}
console.log('Loop complete!');
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
223

delayedLoop();

Each iteration logs after a 1-second delay.

60.

Scenario:
You want to repeatedly execute a function, ensuring each execution completes before
starting the next one.

Question:
How can you implement a polling function that ensures sequential execution?

Answer:
Using async/await, you can ensure that each polling cycle completes before the next one
starts. This avoids overlapping operations.

For Example:

async function poll(interval) {


while (true) {
console.log('Polling...');
await new Promise((resolve) => setTimeout(resolve, interval));
}
}

poll(2000); // Polls every 2 seconds

The polling function logs "Polling..." every 2 seconds, ensuring each execution is sequential.

61.

Scenario:
You need to run multiple asynchronous tasks, but you only want to proceed if a specific
number of them succeed, regardless of the rest.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
224

Question:
How can you implement a logic where you proceed after a minimum number of promises
resolve, using Promise.any?

Answer:
Promise.any resolves with the first fulfilled promise from a list. If all promises are rejected, it
throws an AggregateError. This method is useful when you need at least one success to
continue, ignoring the rest. If no promises succeed, it catches all errors at once.

For Example:

const p1 = Promise.reject('Error from API 1');


const p2 = Promise.resolve('Success from API 2');
const p3 = Promise.resolve('Success from API 3');

Promise.any([p1, p2, p3])


.then((result) => console.log('First Success:', result))
.catch((error) => console.error('All promises failed:', error));

Explanation:

● Promise.any ignores p1 since it fails and returns the first successful promise: p2.
● If all promises reject, it throws an AggregateError.

62.

Scenario:
You need to control how often a function is called to prevent it from being called too
frequently or too slowly.

Question:
How can you combine debounce and throttle to optimize function calls?

Answer:
Debounce delays function execution until a period of inactivity, while throttle ensures a
function runs only once within a given time. Combining them provides smooth control over
frequent events, balancing performance and responsiveness.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
225

function hybrid(func, debounceDelay, throttleLimit) {


let timeout;
let lastCall = 0;

return function (...args) {


const now = Date.now();
clearTimeout(timeout);

if (now - lastCall >= throttleLimit) {


lastCall = now;
func(...args);
} else {
timeout = setTimeout(() => func(...args), debounceDelay);
}
};
}

const optimizedFunc = hybrid(() => console.log('Function executed'), 500, 1000);

// Simulate calls
optimizedFunc();
setTimeout(optimizedFunc, 300); // Delayed call
setTimeout(optimizedFunc, 1200); // Executes after throttle limit

Explanation:
This hybrid function optimizes frequent calls by:

1. Executing regularly with throttle.


2. Running the last event with debounce to avoid missing updates.

63.

Scenario:
You need to retry an API request, with a longer wait time between retries on each failure.

Question:
How can you implement exponential backoff using async/await?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
226

Answer:
Exponential backoff increases the delay after each failed attempt. It is commonly used in
APIs to avoid overloading servers. The retry delay increases exponentially using 2^i logic.

For Example:

async function fetchWithRetry(url, retries, delay) {


for (let i = 0; i <= retries; i++) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error('Fetch failed');
const data = await response.json();
return data;
} catch (error) {
console.error(`Attempt ${i + 1} failed`);
if (i < retries) {
await new Promise((resolve) => setTimeout(resolve, delay * 2 ** i));
} else {
throw new Error('All attempts failed');
}
}
}
}

fetchWithRetry('https://jsonplaceholder.typicode.com/posts', 3, 1000)
.then((data) => console.log('Data:', data))
.catch((error) => console.error('Error:', error));

Explanation:

● Each retry waits longer using 2^i delay increments.


● If all attempts fail, the function throws an error.

64.

Scenario:
You need to run some tasks sequentially while others should run in parallel to improve
performance.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
227

Question:
How can you combine sequential and parallel tasks using async/await?

Answer:
Use await for sequential operations and Promise.all for parallel tasks. This hybrid approach
helps where certain tasks depend on others, while independent tasks can run in parallel.

For Example:

async function runTasks() {


const user = await
fetch('https://jsonplaceholder.typicode.com/users/1').then((res) => res.json());
console.log('User:', user);

const [posts, comments] = await Promise.all([


fetch('https://jsonplaceholder.typicode.com/posts').then((res) => res.json()),
fetch('https://jsonplaceholder.typicode.com/comments').then((res) =>
res.json()),
]);

console.log('Posts:', posts.length);
console.log('Comments:', comments.length);
}

runTasks();

Explanation:

● The user is fetched first (sequential).


● Posts and comments are fetched in parallel for efficiency.

65.

Scenario:
You need to execute a function at regular intervals but ensure each execution completes
before starting the next.

Question:
How can you create a sequential polling system using async/await?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
228

Answer:
A sequential polling system ensures no new polling operation starts until the previous one
completes.

For Example:

async function poll(interval) {


while (true) {
console.log('Polling...');
await new Promise((resolve) => setTimeout(resolve, interval));
}
}

poll(2000); // Logs "Polling..." every 2 seconds sequentially

Explanation:
The polling operation ensures that each iteration waits for the previous one to finish before
starting the next.

66.

Scenario:
You need to cancel a task if it takes too long to complete.

Question:
How can you implement a timeout mechanism to cancel an async task?

Answer:
Using Promise.race, you can cancel an asynchronous task if it exceeds the allowed time.

For Example:

async function fetchWithTimeout(url, timeout) {


const controller = new AbortController();
const signal = controller.signal;

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
229

const fetchPromise = fetch(url, { signal }).then((res) => res.json());


const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject('Timeout!'), timeout)
);

try {
return await Promise.race([fetchPromise, timeoutPromise]);
} catch (error) {
controller.abort(); // Cancel fetch if timeout occurs
throw error;
}
}

fetchWithTimeout('https://jsonplaceholder.typicode.com/posts', 1000)
.then((data) => console.log('Data:', data))
.catch((error) => console.error('Error:', error));

Explanation:
If the fetch exceeds the timeout, the controller cancels it.

67.

Scenario:
You need to process tasks one by one and dynamically add tasks to the queue.

Question:
How can you implement a task queue using JavaScript?

Answer:
A task queue processes tasks sequentially, and tasks can be added dynamically during
runtime.

For Example:

class TaskQueue {
constructor() {
this.queue = [];
this.processing = false;

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
230

addTask(task) {
this.queue.push(task);
this.processTasks();
}

async processTasks() {
if (this.processing) return;
this.processing = true;

while (this.queue.length) {
const task = this.queue.shift();
await task();
}

this.processing = false;
}
}

const queue = new TaskQueue();

queue.addTask(() => new Promise((resolve) => {


console.log('Task 1');
setTimeout(resolve, 1000);
}));

queue.addTask(() => new Promise((resolve) => {


console.log('Task 2');
setTimeout(resolve, 500);
}));

Explanation:

● Tasks are processed one by one.


● Tasks can be added dynamically without disrupting execution.

68.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
231

Scenario:
You need to pause and resume an asynchronous operation based on user input.

Question:
How can you implement pause and resume functionality for an asynchronous task?

Answer:
Use a flag to control whether the task should run or pause. This allows dynamic control over
execution.

For Example:

let paused = false;

function pause() {
paused = true;
}

function resume() {
paused = false;
}

async function runTask() {


while (true) {
if (paused) {
console.log('Paused...');
await new Promise((resolve) => setTimeout(resolve, 1000));
} else {
console.log('Running...');
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
}

runTask();
setTimeout(pause, 3000); // Pause after 3 seconds
setTimeout(resume, 6000); // Resume after 6 seconds

Explanation:
The task pauses and resumes dynamically based on user input.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
232

69.

Scenario:
You need to process multiple tasks but limit how many run concurrently.

Question:
How can you implement a concurrency limiter in JavaScript?

Answer:
A concurrency limiter ensures only a limited number of tasks run at a time.

For Example:

async function limiter(tasks, limit) {


const executing = [];
for (const task of tasks) {
const promise = task();
executing.push(promise);

if (executing.length >= limit) {


await Promise.race(executing);
executing.splice(executing.indexOf(promise), 1);
}
}
await Promise.all(executing);
}

const tasks = Array(5).fill(() =>


new Promise((resolve) => setTimeout(resolve, 1000))
);

limiter(tasks, 2);

Explanation:
At most 2 tasks run at a time, improving control over resource usage.

70.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
233

Scenario:
You need to buffer incoming data streams and process them periodically.

Question:
How can you implement buffering for data streams in JavaScript?

Answer:
Buffering collects data and processes it at intervals to optimize performance.

For Example:

let buffer = [];

function addToBuffer(data) {
buffer.push(data);
}

setInterval(() => {
if (buffer.length) {
console.log('Processing buffer:', buffer);
buffer = [];
}
}, 3000);

addToBuffer('Data 1');
addToBuffer('Data 2');

Explanation:
The buffer processes accumulated data every 3 seconds.

71.

Scenario:
You need to ensure that a set of asynchronous operations runs in sequence, but each
operation depends on the result of the previous one.

Question:
How can you create a dependent promise chain using async/await to ensure sequential
execution?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
234

Answer:
In situations where each asynchronous operation depends on the previous one, using
async/await ensures clear, readable code that executes sequentially. Each operation awaits
the result of the previous one, avoiding nested promise chains.

For Example:

async function runDependentTasks() {


const user = await
fetch('https://jsonplaceholder.typicode.com/users/1').then((res) => res.json());
console.log('User:', user);

const posts = await


fetch(`https://jsonplaceholder.typicode.com/users/${user.id}/posts`).then((res) =>
res.json());
console.log('Posts:', posts.length);

const comments = await


fetch(`https://jsonplaceholder.typicode.com/posts/${posts[0].id}/comments`).then((r
es) => res.json());
console.log('Comments:', comments.length);
}

runDependentTasks();

Explanation:

● The tasks run sequentially.


● Each fetch depends on the result of the previous one (user → posts → comments).

72.

Scenario:
You want to dynamically adjust the delay between two polling cycles based on the response
time or server load.

Question:
How can you implement adaptive polling using async/await?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
235

Answer:
Adaptive polling adjusts the delay between polling cycles based on factors like response
time or server load. This ensures optimal performance and avoids unnecessary network
requests.

For Example:

async function adaptivePoll(fetchFn, minInterval, maxInterval) {


while (true) {
const start = Date.now();
await fetchFn();
const duration = Date.now() - start;

const nextInterval = Math.min(Math.max(duration, minInterval), maxInterval);


console.log(`Waiting for ${nextInterval}ms before next poll`);
await new Promise((resolve) => setTimeout(resolve, nextInterval));
}
}

adaptivePoll(
() => fetch('https://jsonplaceholder.typicode.com/posts').then((res) =>
res.json()),
1000,
5000
);

Explanation:

● The polling interval adjusts based on the response time.


● If the response is slow, the next poll waits longer.

73.

Scenario:
You need to cache API responses to avoid redundant network calls.

Question:
How can you implement a simple in-memory cache using JavaScript to store API results?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
236

Answer:
A cache stores API results to reduce redundant requests and improve performance. You can
use an in-memory cache with an expiration mechanism to keep the data fresh.

For Example:

const cache = {};

async function fetchWithCache(url) {


if (cache[url] && Date.now() - cache[url].timestamp < 30000) {
console.log('Serving from cache:', cache[url].data);
return cache[url].data;
}

const response = await fetch(url);


const data = await response.json();
cache[url] = { data, timestamp: Date.now() };
console.log('Fetching fresh data:', data);
return data;
}

fetchWithCache('https://jsonplaceholder.typicode.com/posts');

Explanation:

● Cached data is served if available and valid.


● Fresh data is fetched if the cache has expired.

74.

Scenario:
You want to run multiple tasks in parallel, but stop all tasks immediately if one of them fails.

Question:
How can you abort parallel tasks upon the first failure using Promise.all?

Answer:
Promise.all runs tasks in parallel and rejects immediately if any task fails. This is useful
when the result depends on all tasks succeeding.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
237

For Example:

const task1 = () => new Promise((resolve) => setTimeout(resolve, 1000, 'Task 1'));
const task2 = () => new Promise((_, reject) => setTimeout(reject, 500, 'Task 2
failed'));
const task3 = () => new Promise((resolve) => setTimeout(resolve, 1500, 'Task 3'));

Promise.all([task1(), task2(), task3()])


.then((results) => console.log('Results:', results))
.catch((error) => console.error('Error:', error));

Explanation:

● If task2 fails, all tasks are aborted, and the error is logged.

75.

Scenario:
You need to periodically refresh the cache to ensure your data stays up to date.

Question:
How can you implement automatic cache invalidation using setInterval?

Answer:
Use setInterval to periodically clear outdated cache entries, ensuring your data remains
accurate and fresh.

For Example:

const cache = {};

function invalidateCache() {
for (const key in cache) {
if (Date.now() - cache[key].timestamp > 30000) {
console.log(`Invalidating cache for: ${key}`);
delete cache[key];
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
238

}
}

setInterval(invalidateCache, 10000); // Run every 10 seconds

async function fetchWithCache(url) {


if (cache[url]) {
console.log('Serving from cache:', cache[url].data);
return cache[url].data;
}

const response = await fetch(url);


const data = await response.json();
cache[url] = { data, timestamp: Date.now() };
return data;
}

Explanation:

● The cache is cleared every 10 seconds for stale entries.


● Data is refreshed if needed.

76.

Scenario:
You need to rate-limit API requests to avoid overloading the server.

Question:
How can you implement a rate limiter using JavaScript?

Answer:
A rate limiter ensures only a limited number of requests are made within a specified time
frame.

For Example:

class RateLimiter {
constructor(limit, interval) {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
239

this.limit = limit;
this.interval = interval;
this.requests = 0;
this.queue = [];
}

scheduleRequest(requestFn) {
if (this.requests < this.limit) {
this.requests++;
requestFn().finally(() => this.reset());
} else {
this.queue.push(requestFn);
}
}

reset() {
setTimeout(() => {
this.requests--;
if (this.queue.length) this.scheduleRequest(this.queue.shift());
}, this.interval);
}
}

const limiter = new RateLimiter(2, 1000);

const request = () =>


fetch('https://jsonplaceholder.typicode.com/posts').then((res) => res.json());

limiter.scheduleRequest(request);
limiter.scheduleRequest(request);
limiter.scheduleRequest(request);

Explanation:

● Only two requests are made per second.


● Excess requests are queued and executed later.

77.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
240

Scenario:
You need to detect memory leaks caused by uncollected promises.

Question:
How can you track unresolved promises to prevent memory leaks?

Answer:
You can track unresolved promises by keeping references to them and checking
periodically if they are resolved.

For Example:

const unresolvedPromises = new Set();

function trackPromise(promise) {
unresolvedPromises.add(promise);
promise.finally(() => unresolvedPromises.delete(promise));
}

setInterval(() => {
console.log(`Unresolved promises: ${unresolvedPromises.size}`);
}, 5000);

// Simulate promises
const p1 = new Promise((resolve) => setTimeout(resolve, 1000));
const p2 = new Promise((resolve) => setTimeout(resolve, 5000));

trackPromise(p1);
trackPromise(p2);

Explanation:

● The unresolved promises are tracked and monitored every 5 seconds.


● This helps identify promises that may cause memory leaks.

78.

Scenario:
You need to batch API requests and send them together to optimize performance.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
241

Question:
How can you implement request batching using JavaScript?

Answer:
Batching groups multiple requests and sends them together to reduce overhead and
improve performance.

For Example:

let batch = [];

function addToBatch(request) {
batch.push(request);
}

function sendBatch() {
console.log('Sending batch:', batch);
batch = [];
}

setInterval(() => {
if (batch.length) sendBatch();
}, 3000); // Send batch every 3 seconds

addToBatch('Request 1');
addToBatch('Request 2');

Explanation:

● Requests are batched and sent every 3 seconds.


● This reduces the frequency of network calls.

79.

Scenario:
You need to process a large data stream without blocking the main thread.

Question:
How can you use Web Workers to process data in the background?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
242

Answer:
Web Workers allow you to run JavaScript code in the background thread to prevent
blocking the main UI.

For Example:

// worker.js
self.onmessage = function (e) {
const result = e.data * 2;
postMessage(result);
};

// main.js
const worker = new Worker('worker.js');

worker.onmessage = function (e) {


console.log('Result from worker:', e.data);
};

worker.postMessage(10);

Explanation:

● The worker processes data in the background.


● The main thread remains responsive.

80.

Scenario:
You need to retry failed tasks with delays, but only for certain error types.

Question:
How can you implement conditional retries based on error types?

Answer:
Conditional retries allow you to retry tasks only for specific error conditions, improving
efficiency.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
243

// worker.js
self.onmessage = function (e) {
const result = e.data * 2;
postMessage(result);
};

// main.js
const worker = new Worker('worker.js');

worker.onmessage = function (e) {


console.log('Result from worker:', e.data);
};

worker.postMessage(10);

Explanation:

● Retries are only attempted for network-related errors.


● Other errors are thrown immediately.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
244

Chapter 5 : Error Handling and Debugging


THEORETICAL QUESTIONS
1. What are the types of errors in JavaScript?

Answer: In JavaScript, errors are generally categorized into three main types: syntax errors,
runtime errors, and logical errors.

● Syntax Errors occur when the code is not written correctly according to JavaScript
syntax rules. These errors prevent the script from running at all. For instance, missing
parentheses or an unexpected token can cause syntax errors.
● Runtime Errors are errors that occur while the code is running. Even though the
syntax is correct, something unexpected happens during execution, such as trying to
access a variable that doesn't exist.
● Logical Errors are the hardest to detect. These occur when the code runs without any
issues, but it doesn’t produce the expected output due to a mistake in the logic.

For Example:

let x = 10;
let y = 20;
console.log(x * y); // This will work fine.

2. What is exception handling in JavaScript?

Answer: Exception handling in JavaScript is a mechanism to handle runtime errors


gracefully. Instead of letting the program crash, you can catch and manage errors using try,
catch, throw, and finally blocks. This helps in ensuring that the program runs smoothly
even when errors occur.

● Try block contains the code that may throw an error.


● Catch block allows you to handle the error if one occurs in the try block.
● Throw allows you to manually throw an error.
● Finally executes code after the try-catch block, regardless of whether an error
occurred or not.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
245

For Example:

try {
let result = someFunction(); // Assume someFunction is not defined
} catch (error) {
console.error("An error occurred: ", error.message);
} finally {
console.log("This will always run, error or not.");
}

3. What is a syntax error in JavaScript? Provide an example.

Answer: A syntax error occurs when the JavaScript code doesn't follow proper syntax. These
errors prevent the script from being executed at all. Syntax errors are generally caught by the
JavaScript engine during the parsing phase before the code is executed. Common syntax
errors include missing brackets, unclosed strings, or improper keyword usage.

For Example:

if (x === 10 // Syntax Error: Missing closing parenthesis


{
console.log("X is 10");
}

In this example, the missing closing parenthesis will cause a syntax error, and the script won’t
run until it's corrected.

4. What is a runtime error in JavaScript? Give an example.

Answer: A runtime error occurs when the JavaScript code is syntactically correct but fails to
execute properly. This type of error occurs after the program has started running. Examples
of runtime errors include trying to access variables that are not defined or trying to call a
function that doesn't exist.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
246

try {
let result = someFunction(); // someFunction is not defined
} catch (error) {
console.log("Caught an error:", error.message);
}

In this case, the code tries to execute a function that is not defined, resulting in a runtime
error. The error is caught in the catch block.

5. What is a logical error in JavaScript? Provide an example.

Answer: A logical error in JavaScript occurs when the code runs without generating any
errors, but it doesn't produce the correct or expected output. These errors are due to flaws in
the logic of the code, and they are difficult to catch because the code doesn't actually "break."

For Example:

function calculateArea(length, width) {


return length + width; // Logical Error: should be multiplication
}
console.log(calculateArea(5, 10)); // Output: 15, but expected 50

In this case, the code is supposed to calculate the area of a rectangle, but due to a logical
error (using addition instead of multiplication), it returns the wrong result.

6. What is a try-catch block in JavaScript, and how is it used?

Answer: A try-catch block in JavaScript is used to handle exceptions that may occur during
the execution of code. The try block contains code that might throw an error, and the catch
block contains code to handle that error if it occurs. Optionally, you can add a finally block,
which will execute regardless of whether an error occurs or not.

For Example:

try {
let result = divideNumbers(10, 0);
} catch (error) {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
247

console.error("An error occurred:", error.message);


} finally {
console.log("This will run no matter what.");
}

function divideNumbers(a, b) {
if (b === 0) {
throw new Error("Cannot divide by zero");
}
return a / b;
}

In this case, the try block attempts to divide numbers, and if a division by zero occurs, an
error is thrown and caught in the catch block.

7. How do you manually throw an error in JavaScript?

Answer: You can manually throw an error in JavaScript using the throw statement. This
allows you to create custom errors based on specific conditions in your code. When an error
is thrown, it interrupts the normal flow of execution and transfers control to the nearest
catch block, where you can handle the error.

For Example:

function checkAge(age) {
if (age < 18) {
throw new Error("You must be at least 18 years old");
}
console.log("Access granted");
}

try {
checkAge(15);
} catch (error) {
console.error(error.message);
}

In this example, the throw statement is used to manually create an error when the age is less
than 18. This error is then caught and handled in the catch block.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
248

8. What are custom errors in JavaScript, and how can they be created?

Answer: Custom errors in JavaScript are user-defined errors that you can throw based on
specific conditions in your code. JavaScript provides built-in error types like TypeError and
RangeError, but you can also create your own custom error types by extending the built-in
Error object.

For Example:

class CustomError extends Error {


constructor(message) {
super(message);
this.name = "CustomError";
}
}

try {
throw new CustomError("This is a custom error");
} catch (error) {
console.error(`${error.name}: ${error.message}`);
}

In this example, a new CustomError class is created by extending the built-in Error class. The
custom error can then be thrown and caught just like any other error.

9. What are browser developer tools, and how are they used for debugging?

Answer: Browser developer tools are built-in features in modern web browsers that help
developers inspect, debug, and optimize their code. These tools provide a variety of
debugging features like inspecting the Document Object Model (DOM), analyzing network
requests, viewing console logs, and stepping through JavaScript code.

For Example: In Google Chrome:

● You can open developer tools by pressing F12 or right-clicking on the page and
selecting "Inspect."
● Navigate to the "Console" tab to view logs, errors, and other debug messages.
● Use the "Sources" tab to set breakpoints in your JavaScript code and step through the
execution.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
249

These tools are invaluable for debugging issues in real-time, allowing developers to see how
their code behaves during execution.

10. What are the console methods available in JavaScript for debugging?

Answer: JavaScript's console object provides several methods to output information,


warnings, and errors, which are very useful for debugging. The most commonly used
methods are:

● console.log(): Outputs general information and messages.


● console.warn(): Displays warning messages.
● console.error(): Logs errors, often with a stack trace.
● console.group() and console.groupEnd(): Organizes logs into groups.
● console.table(): Displays data in a table format.

For Example:

console.log("This is a log message");


console.warn("This is a warning");
console.error("This is an error");
console.group("Grouped Messages");
console.log("First message in group");
console.log("Second message in group");
console.groupEnd();
console.table([{ name: "Alice", age: 30 }, { name: "Bob", age: 25 }]);

These methods help track different types of information during development and make
debugging more efficient.

11. What is the difference between try-catch and finally in JavaScript?

Answer:
The try-catch block helps in handling errors that occur during code execution, while the
finally block is used for code that must execute regardless of whether an error occurs. If no
error is thrown, the catch block is skipped, but the finally block still executes. This ensures
that essential cleanup tasks (like closing resources) are completed.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
250

For Example:

try {
let result = someOperation(); // Assume someOperation is a function
} catch (error) {
console.error("Error occurred:", error.message);
} finally {
console.log("This will always execute.");
}

The above code demonstrates how finally ensures critical code executes even if there is an
error.

12. Can a catch block have multiple parameters in JavaScript?

Answer:
The catch block only accepts a single parameter (an error object). Inside the catch, the
error object provides detailed information about the exception, including properties like name
(type of error) and message (error message). If you need to handle multiple details, you
extract them from this single error object.

For Example:

try {
throw new ReferenceError("This is a reference error");
} catch (error) {
console.log(`Error Type: ${error.name}`); // "ReferenceError"
console.log(`Error Message: ${error.message}`); // "This is a reference error"
}

Here, even though the catch only has one parameter, you can extract multiple properties
from the error.

13. What happens if there is no catch block in a try block?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
251

Answer:
If a try block does not have a catch block and an error occurs, the script will throw the error
and terminate the execution unless the error is handled at some higher level. However, if you
only need to perform cleanup operations, you can omit the catch block and use a finally
block to run code regardless of success or failure.

For Example:

try {
someUndefinedFunction(); // This will throw a ReferenceError
} finally {
console.log("Cleanup executed.");
}
// Error is not caught, so the program will stop after this block.

This example shows how an error causes the script to stop, but the finally block still runs
before termination.

14. Can you nest try-catch blocks in JavaScript?

Answer:
Yes, JavaScript allows nested try-catch blocks. This means you can place one try-catch
block inside another. This is useful when different errors need to be handled separately, or
when an error in the inner block must be handled differently from an error in the outer block.

For Example:

try {
try {
throw new Error("Inner error");
} catch (innerError) {
console.log("Handled inner error:", innerError.message);
}
throw new Error("Outer error");
} catch (outerError) {
console.log("Handled outer error:", outerError.message);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
252

This example demonstrates how the inner and outer try-catch blocks handle their
respective errors independently.

15. What is the purpose of the finally block?

Answer:
The finally block ensures that certain code always executes, no matter what happens in
the try-catch block. This is especially useful for cleanup operations—for example, closing
files, releasing database connections, or stopping loading indicators after an API call.

For Example:

try {
let data = fetchData(); // Assume fetchData makes an API call
} catch (error) {
console.error("API call failed:", error.message);
} finally {
console.log("This will run, regardless of API success or failure.");
}

Even if the fetchData function throws an error, the code inside finally will still run.

16. What are built-in error types in JavaScript?

Answer:
JavaScript provides several built-in error types to represent different kinds of errors:

● Error: A general-purpose error.


● TypeError: Thrown when a value isn’t of the expected type (e.g., calling a number like
a function).
● ReferenceError: Thrown when trying to access an undeclared variable.
● SyntaxError: Occurs when the code has invalid syntax.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
253

● RangeError: Thrown when a value is outside the allowable range (e.g., too large an
array size).

For Example:

try {
console.log(x); // ReferenceError: x is not defined
} catch (error) {
console.error(`${error.name}: ${error.message}`);
}

Here, the ReferenceError occurs because the variable x is not defined.

17. How do you handle asynchronous errors in JavaScript?

Answer:
Asynchronous errors occur when working with async operations like API calls or file I/O.
JavaScript provides async/await syntax and Promises to handle these errors. The try-catch
block can also be used inside an async function to catch these errors.

For Example:

async function fetchData() {


try {
let response = await fetch("https://api.example.com/data");
let data = await response.json();
console.log(data);
} catch (error) {
console.error("Error fetching data:", error.message);
}
}
fetchData();

In this example, any error during the fetch operation is caught by the try-catch block.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
254

18. What is the difference between console.error() and console.warn()?

Answer:

● console.error(): Displays errors and often provides a stack trace, helping


developers trace the origin of the error.
● console.warn(): Displays a warning message, indicating a potential issue, but not
necessarily something that stops execution.

For Example:

console.error("This is an error message");


console.warn("This is a warning message");

Errors are more severe than warnings, but both are useful for tracking issues.

19. How do you debug JavaScript code with breakpoints?

Answer:
Breakpoints allow you to pause code execution at specific lines and inspect variables during
runtime. This is especially useful for identifying issues that aren’t immediately obvious from
the code.

For Example:

1. Open Developer Tools by pressing F12 in Chrome.


2. Navigate to the "Sources" tab.
3. Click on the line number where you want to set a breakpoint.
4. Refresh the page or trigger the code, and execution will pause at the breakpoint.

You can then step through the code using buttons in the developer tools to observe variable
states.

20. How does console.group() and console.groupEnd() work in JavaScript?

Answer:
The console.group() method allows you to group multiple logs into a collapsible structure,

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
255

which improves readability. console.groupEnd() closes the group. This is useful for
organizing related log messages.

For Example:

console.group("User Information");
console.log("Name: Alice");
console.log("Age: 30");
console.group("Address");
console.log("City: New York");
console.groupEnd(); // Ends "Address" group
console.groupEnd(); // Ends "User Information" group

This example organizes logs into nested groups, making it easier to navigate large sets of
console messages.

21. How do you rethrow an error inside a catch block in JavaScript?

Answer:
Rethrowing an error inside a catch block allows you to perform some initial handling (like
logging) and then let the error propagate for further handling elsewhere. This is especially
useful if an error needs different treatments at different levels of your application.

For Example:

function process() {
try {
throw new Error("Initial error");
} catch (error) {
console.warn("Handling error:", error.message); // First handling
throw error; // Rethrow the error
}
}

try {
process();

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
256

} catch (error) {
console.error("Caught rethrown error:", error.message); // Final handling
}

Here, the error is caught and logged in the first catch block but rethrown to be handled at
the outer level. This ensures you don’t lose the original error context.

22. How can you create a custom error with additional properties?

Answer:
Creating custom errors allows you to provide more context about the problem by adding
custom properties. You can extend the built-in Error class and add extra data like status
codes or fields where the error occurred.

For Example:

class ValidationError extends Error {


constructor(message, field) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}

try {
throw new ValidationError("Invalid email format", "email");
} catch (error) {
console.error(`${error.name}: ${error.message} in ${error.field}`);
}

This example creates a ValidationError that provides extra information about which field
caused the error.

23. What is a stack trace in JavaScript, and how can it help in debugging?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
257

Answer:
A stack trace is a detailed list of function calls that shows the path through which the code
execution reached the point where an error occurred. It helps identify the sequence of
function calls leading to the issue and shows the exact file and line number where the error
happened.

For Example:

function a() {
b();
}

function b() {
c();
}

function c() {
throw new Error("Something went wrong");
}

try {
a();
} catch (error) {
console.error(error.stack); // Stack trace of the error
}

The output shows the sequence: c() → b() → a(), helping you trace the origin of the issue.

24. How do you handle multiple asynchronous operations with Promise.all()


and manage errors?

Answer:
Promise.all() is used to execute multiple promises in parallel and wait for all of them to
resolve. If one promise fails, the entire operation is rejected. You can catch errors using try-
catch inside an async function or with .catch().

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
258

const promise1 = Promise.resolve(10);


const promise2 = Promise.reject(new Error("Failed promise"));
const promise3 = Promise.resolve(30);

async function executeAll() {


try {
let results = await Promise.all([promise1, promise2, promise3]);
console.log(results);
} catch (error) {
console.error("Error in Promise.all:", error.message);
}
}

executeAll();

Here, if any promise fails, the entire operation fails, and the error is caught in the catch block.

25. What is the difference between synchronous and asynchronous error


handling?

Answer:

● Synchronous error handling: Errors are caught immediately in sequential code using
try-catch.
● Asynchronous error handling: Errors in asynchronous code (like Promises) are
handled with .catch() or inside an async function using try-catch.

For Example: Synchronous:

try {
throw new Error("Synchronous error");
} catch (error) {
console.log(error.message);
}

Asynchronous:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
259

async function asyncError() {


throw new Error("Asynchronous error");
}

asyncError().catch((error) => console.log(error.message));

In the asynchronous example, .catch() handles the promise rejection.

26. How do you debug memory leaks in JavaScript applications?

Answer:
Memory leaks occur when allocated memory is not released, causing the application to
consume more memory over time. You can use browser developer tools to find and fix such
leaks. Common causes include:

● Uncleared event listeners.


● Global variables that persist.
● Long-living references to DOM elements.

For Example:

const elements = [];


function createLeak() {
elements.push(document.createElement("div"));
}
setInterval(createLeak, 1000); // Memory leak: keeps adding elements indefinitely

In this code, elements keep accumulating in memory without being removed, leading to a
leak.

27. How do you use debugger statements for debugging JavaScript code?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
260

Answer:
The debugger statement pauses the code execution, allowing you to inspect variables and
control flow in the browser’s developer tools. It behaves like a manual breakpoint.

For Example:

function debugExample() {
let x = 10;
debugger; // Execution pauses here
let y = 20;
console.log(x + y);
}

debugExample();

When the browser encounters the debugger statement, it will stop execution, letting you
inspect variables like x and y at that point.

28. What is async/await error handling, and how does it improve over .then()
and .catch()?

Answer:
Using async/await makes asynchronous code look and behave like synchronous code,
making it easier to read and maintain. Instead of chaining .then() and .catch(), you can
use try-catch for clean error handling.

For Example: Using .then() and .catch():

fetch('https://api.example.com/data')
.then(response => response.json())
.catch(error => console.error("Error:", error.message));

Using async/await:

async function getData() {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
261

try {
let response = await fetch('https://api.example.com/data');
let data = await response.json();
console.log(data);
} catch (error) {
console.error("Error:", error.message);
}
}
getData();

With async/await, the code is cleaner, and errors are handled using try-catch.

29. How do you log errors to a server for better tracking and analysis?

Answer:
Logging errors to a server allows you to track and analyze issues in production. This helps
monitor application performance and identify patterns in user-reported errors. You can send
error logs using fetch or any HTTP library.

For Example:

function logError(error) {
fetch('https://logging-server.com/errors', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: error.message, stack: error.stack })
});
}

try {
throw new Error("An unexpected error");
} catch (error) {
console.error("Logging error:", error.message);
logError(error);
}

This example demonstrates how to log errors remotely for future analysis.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
262

30. What are source maps, and how do they help in debugging minified
JavaScript code?

Answer:
Source maps link the minified code to the original source code, making it easier to debug
production code. When JavaScript is minified, variable names are shortened, and the
structure becomes hard to read. Source maps allow browsers to map the minified code
back to its original form.

For Example:
If a minified JavaScript file includes:

//# sourceMappingURL=script.min.js.map

The browser will use the script.min.js.map file to show the original code in developer tools,
making it easier to understand and debug issues.

This feature is essential for debugging production environments without exposing the raw
source code directly.

31. How can you handle multiple errors within the same try-catch block?

Answer:
Handling multiple error types within the same catch block ensures that you can respond
differently to different error scenarios. You typically inspect the error object’s name or
instanceof to determine the type of error and handle each case accordingly. This technique
avoids scattering try-catch blocks and keeps the logic centralized.

For Example:

try {
let result = someUndefinedFunction(); // Throws ReferenceError
} catch (error) {
if (error instanceof ReferenceError) {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
263

console.error("ReferenceError:", error.message);
} else if (error instanceof TypeError) {
console.error("TypeError:", error.message);
} else {
console.error("General Error:", error.message);
}
}

In this example, different error types like ReferenceError and TypeError are identified and
handled uniquely, helping to debug more effectively.

32. What is the purpose of the window.onerror handler in JavaScript?

Answer:
The window.onerror function acts as a global error handler for any uncaught errors in the
JavaScript code. This is particularly useful in production environments where unexpected
errors might occur. By capturing the error globally, you can log it to a server or display a
fallback UI to prevent the application from breaking completely.

For Example:

window.onerror = function (message, source, lineno, colno, error) {


console.error(`Error: ${message} at ${source}:${lineno}:${colno}`);
return true; // Prevents the default browser error handling
};

nonExistentFunction(); // This triggers window.onerror

Here, window.onerror logs detailed information about where and how the error occurred,
helping with debugging and recovery.

33. How do you implement a retry mechanism for failed operations in JavaScript?

Answer:
A retry mechanism attempts to re-run an operation a few times in case of failure. This is

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
264

commonly used for network requests, where transient errors like time-outs or server
unavailability can occur. The idea is to retry the operation with a delay to increase the
chance of success.

For Example:

async function fetchWithRetry(url, retries = 3) {


try {
let response = await fetch(url);
if (!response.ok) throw new Error("Fetch failed");
return await response.json();
} catch (error) {
if (retries > 0) {
console.warn(`Retrying... (${retries} retries left)`);
return fetchWithRetry(url, retries - 1);
} else {
throw error;
}
}
}

fetchWithRetry("https://api.example.com/data").catch((error) =>
console.error("Failed after multiple retries:", error.message)
);

This code retries the fetch operation until it either succeeds or exhausts the retry attempts.

34. What is the difference between throw and return in error handling?

Answer:

● throw: Interrupts the function’s execution and passes control to the nearest catch
block.
● return: Ends the function and sends a value back to the caller, without raising an
error.

Use throw when you want to indicate that something unexpected or invalid occurred, while
return is used to provide expected results or status values.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
265

For Example:

function checkValue(value) {
if (value < 0) throw new Error("Negative value not allowed");
return value;
}

try {
console.log(checkValue(-1)); // Throws an error
} catch (error) {
console.error("Caught Error:", error.message);
}

In this example, if the value is invalid, the function throws an error; otherwise, it returns the
valid value.

35. How can you create and use Error subclasses to handle different error
scenarios?

Answer:
Creating custom error classes by extending the Error class allows you to define specific
types of errors for different parts of your application, such as DatabaseError or
ValidationError. This makes it easier to categorize and handle errors appropriately.

For Example:

class DatabaseError extends Error {


constructor(message) {
super(message);
this.name = "DatabaseError";
}
}

try {
throw new DatabaseError("Database connection failed");
} catch (error) {
if (error instanceof DatabaseError) {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
266

console.error("Handled DatabaseError:", error.message);


} else {
console.error("Unhandled error:", error.message);
}
}

This code defines a DatabaseError class and allows specific handling for database-related
issues.

36. How can you use Promise.race() to handle timeout errors?

Answer:
Promise.race() returns the result of the first promise to settle (resolve or reject). You can
use this feature to set a timeout for an operation. If the timeout promise settles first, the
operation is considered failed.

For Example:

function timeout(ms) {
return new Promise((_, reject) =>
setTimeout(() => reject(new Error("Timeout exceeded")), ms)
);
}

async function fetchWithTimeout(url, ms) {


try {
const response = await Promise.race([fetch(url), timeout(ms)]);
return await response.json();
} catch (error) {
console.error("Error:", error.message);
}
}

fetchWithTimeout("https://api.example.com/data", 5000);

If the fetch operation takes longer than 5 seconds, the timeout promise rejects the
operation.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
267

37. How do you perform structured logging with custom error objects?

Answer:
Structured logging involves logging error data along with contextual information, such as
user ID, timestamp, and error type. This helps developers analyze logs more effectively in
production systems.

For Example:

class AppError extends Error {


constructor(message, userId) {
super(message);
this.name = "AppError";
this.userId = userId;
this.timestamp = new Date();
}
}

try {
throw new AppError("Operation failed", 12345);
} catch (error) {
console.log(JSON.stringify(error, Object.getOwnPropertyNames(error)));
}

This example demonstrates how to log an error along with additional metadata, such as
userId and timestamp.

38. What are uncaught promise rejections, and how can you handle them
globally?

Answer:
An uncaught promise rejection occurs when a rejected promise doesn’t have a .catch()
block. These errors can crash the application if not handled. To manage them, use the
unhandledrejection event.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
268

window.addEventListener("unhandledrejection", (event) => {


console.error("Unhandled promise rejection:", event.reason);
});

Promise.reject(new Error("Promise failed")); // Triggers unhandledrejection

This example captures all unhandled promise rejections globally and logs them.

39. How do you debug and handle cyclic errors in JSON using try-catch?

Answer:
Cyclic errors occur when an object contains circular references, making it impossible to
convert it to JSON. You can catch these errors with try-catch.

For Example:

const obj = {};


obj.self = obj; // Cyclic reference

try {
JSON.stringify(obj); // This throws an error
} catch (error) {
console.error("Cyclic error:", error.message);
}

The error is caught, preventing the application from crashing due to the circular reference.

40. How do you handle errors gracefully in a user interface?

Answer:
In a UI, graceful error handling involves showing user-friendly messages instead of
technical errors. This keeps the user experience smooth and prevents the interface from
breaking.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
269

For Example (React):

function ErrorBoundary({ error }) {


return (
<div>
<h2>Something went wrong</h2>
<p>{error.message}</p>
<button onClick={() => window.location.reload()}>Try Again</button>
</div>
);
}

try {
throw new Error("Unexpected error");
} catch (error) {
ReactDOM.render(<ErrorBoundary error={error} />,
document.getElementById("root"));
}

This example displays a friendly error message and provides a way to reload the page,
ensuring the user can recover from the error easily.

SCENARIO QUESTIONS
41. Scenario: You are developing a form validation system, and the form crashes
because of an invalid variable name in JavaScript code.

Question: How would you identify and resolve a syntax error in this scenario?

Answer:
Syntax errors occur when the code is incorrectly written according to JavaScript rules. These
errors prevent code from running and are usually flagged during compilation or by the
browser before execution. In the form validation scenario, the console will indicate the exact
line and character where the error occurred.

To resolve:

1. Use Developer Tools (F12): Go to the Console tab to see the syntax error.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
270

2. Correct missing characters like brackets or semicolons.


3. Use a linter such as ESLint to catch such errors early.

For Example:

let formValid = true;


if (formValid // Syntax Error: Missing closing parenthesis
console.log("Form is valid");
}

Fix:

if (formValid) {
console.log("Form is valid");
}

42. Scenario: You call a function that doesn’t exist, and your application crashes
with an error.

Question: How would you handle runtime errors in such scenarios?

Answer:
A runtime error occurs when the code structure is correct, but execution fails due to invalid
operations, such as calling an undefined function. Runtime errors are often due to mistyped
function names or unavailable dependencies. Use try-catch blocks to handle them
gracefully, so the rest of the program can run without interruption.

For Example:

try {
nonExistentFunction(); // ReferenceError: function is not defined
} catch (error) {
console.error("Caught an error:", error.message);
}
console.log("The program continues.");

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
271

Here, even though the function doesn’t exist, the catch block ensures that the error is
logged, and the rest of the program executes.

43. Scenario: You mistakenly implement a discount calculation that adds prices
instead of subtracting a discount.

Question: How would you detect and debug a logical error in JavaScript?

Answer:
Logical errors occur when the code executes successfully but doesn’t produce the correct
output due to flawed logic. These are hard to detect since no syntax or runtime errors occur.
The best way to debug logical errors is by:

1. Using console.log() to track variables.


2. Using breakpoints in the Developer Tools to pause and inspect code execution.
3. Writing unit tests to confirm correct logic.

For Example:

function calculateDiscount(price, discount) {


return price + discount; // Logical Error: Should subtract discount
}
console.log(calculateDiscount(100, 10)); // Output: 110, Expected: 90

Corrected:

return price - discount;

44. Scenario: You are building an API request function, and you want to ensure
the application does not crash when the API call fails.

Question: How would you handle errors gracefully with try-catch in asynchronous code?

Answer:
In asynchronous operations like fetch requests, errors can occur due to network issues or
invalid URLs. Use try-catch blocks within an async function to handle these errors
gracefully.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
272

For Example:

async function fetchData(url) {


try {
let response = await fetch(url);
if (!response.ok) throw new Error("Fetch failed");
let data = await response.json();
console.log(data);
} catch (error) {
console.error("Error fetching data:", error.message);
}
}

fetchData("https://api.example.com/data");

This ensures that even if the API call fails, the error is caught and logged without crashing the
app.

45. Scenario: A button click triggers an undefined event handler, causing the
application to crash.

Question: How would you handle errors caused by undefined event handlers in JavaScript?

Answer:
If a button click triggers an undefined event handler, a ReferenceError is thrown. Use try-
catch blocks to handle such cases and prevent the application from crashing.

For Example:

let button = document.querySelector("#myButton");

button.addEventListener("click", () => {
try {
eventHandler(); // ReferenceError: eventHandler is not defined
} catch (error) {
console.error("Error:", error.message);
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
273

});

This ensures that even if the event handler is undefined, the error is logged, and the rest of
the program continues to run.

46. Scenario: A function throws an error if a parameter is invalid, and you want to
throw custom error messages.

Question: How would you create and throw custom errors in JavaScript?

Answer:
You can throw custom errors to provide meaningful error messages when specific
conditions are not met. This improves readability and helps users or developers understand
the problem.

For Example:

function validateAge(age) {
if (age < 18) throw new Error("Age must be 18 or above");
console.log("Age is valid.");
}

try {
validateAge(16);
} catch (error) {
console.error("Validation Error:", error.message);
}

Here, a custom error message is thrown if the age is below 18, and the error is caught to
prevent the program from crashing.

47. Scenario: You have a recursive function, and you suspect it’s causing a
memory leak.

Question: How would you debug and handle memory leaks in JavaScript?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
274

Answer:
A memory leak occurs when memory that is no longer needed isn’t released. Use the
Memory tab in browser Developer Tools to monitor memory usage over time. Remove
unnecessary references or use WeakMap to handle temporary data.

For Example:

const elements = [];


function createDiv() {
elements.push(document.createElement("div"));
}
setInterval(createDiv, 1000); // Memory leak: elements accumulate endlessly

Use clearInterval to stop the function or periodically clear the elements array to avoid
memory build-up.

48. Scenario: You want to group multiple console messages together for better
readability.

Question: How would you use console.group() and console.groupEnd() for debugging?

Answer:
Use console.group() and console.groupEnd() to group related logs, making them easier
to read and navigate in the console.

For Example:

console.group("User Information");
console.log("Name: John Doe");
console.log("Age: 30");
console.group("Address");
console.log("City: New York");
console.groupEnd(); // End Address group
console.groupEnd(); // End User Information group

This groups the logs, creating a structured output.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
275

49. Scenario: You need to ensure that some code executes regardless of whether
an error occurs.

Question: How would you use a finally block in JavaScript?

Answer:
A finally block ensures that code inside it runs no matter what—whether the try block
succeeds or an error occurs. This is useful for cleanup tasks.

For Example:

try {
let data = JSON.parse('{"key": "value"}');
} catch (error) {
console.error("Error:", error.message);
} finally {
console.log("Cleanup completed.");
}

Here, the finally block runs even if the parsing fails or succeeds.

50. Scenario: You have a button that triggers multiple operations, and you want to
log all steps clearly.

Question: How would you use console.table() to display data neatly in the console?

Answer:
Use console.table() to log data in a tabular format, making it easier to analyze arrays or
objects.

For Example:

const users = [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 30 },

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
276

{ name: "Charlie", age: 35 }


];

console.table(users);

This displays the user data in a readable table format in the console.

51. Scenario: You have multiple asynchronous functions, and one of them fails. You
want to continue executing the rest instead of stopping.

Question: How would you handle errors in Promise.allSettled() to manage partial


success?

Answer:
Using Promise.allSettled() allows you to execute multiple asynchronous operations and
get a result for each—whether fulfilled or rejected. This ensures that one failed promise does
not stop the others from completing. This is useful when working with independent tasks
where you want to log or process all results, regardless of success or failure.

For Example:

const promises = [
Promise.resolve("Operation 1 success"),
Promise.reject("Operation 2 failed"),
Promise.resolve("Operation 3 success")
];

Promise.allSettled(promises).then((results) => {
results.forEach((result) => {
if (result.status === "fulfilled") {
console.log("Success:", result.value);
} else {
console.warn("Failure:", result.reason);
}
});
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
277

Even though one promise fails, the others continue to execute, and all results are logged.

52. Scenario: You want to measure the performance of different parts of your code
for debugging.

Question: How would you use console.time() and console.timeEnd() to measure


execution time?

Answer:
console.time() and console.timeEnd() help measure the execution time of a specific
block of code. This is useful for performance testing to detect bottlenecks.

For Example:

console.time("Loop Execution");

for (let i = 0; i < 1000000; i++) {


// Simulate workload
}

console.timeEnd("Loop Execution");

The console will display how long it took for the loop to execute, helping you identify slow
code sections.

53. Scenario: A promise is rejected, but the application behaves unexpectedly


because the error isn’t logged.

Question: How would you ensure all promise rejections are handled properly?

Answer:
Uncaught promise rejections can lead to unpredictable behavior. Using the
unhandledrejection event ensures that even if promises don’t have .catch() blocks, the
rejections are logged and managed globally.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
278

window.addEventListener("unhandledrejection", (event) => {


console.error("Unhandled promise rejection:", event.reason);
});

Promise.reject("This is an unhandled rejection"); // This triggers the event

This ensures no unhandled promise rejection goes unnoticed, improving error tracking.

54. Scenario: You are working with large datasets and want to prevent freezing
the browser with synchronous loops.

Question: How would you use setTimeout to avoid blocking the UI?

Answer:
Long synchronous loops block the event loop, freezing the UI. Breaking tasks into smaller
pieces using setTimeout() allows the browser to handle UI updates between iterations.

For Example:

let i = 0;
function processLargeData() {
if (i < 100000) {
console.log(i++);
setTimeout(processLargeData, 0); // Prevents UI freeze
}
}
processLargeData();

This approach keeps the browser responsive while processing large datasets.

55. Scenario: You have a recursive function, but it reaches the call stack limit.

Question: How would you avoid a stack overflow with tail call optimization?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
279

Answer:
Tail call optimization (TCO) reuses the current stack frame for certain recursive calls,
preventing stack overflow. This optimization occurs if the recursive call is the last operation
in the function.

For Example:

function sum(n, acc = 0) {


if (n === 0) return acc;
return sum(n - 1, acc + n); // Tail recursion
}

console.log(sum(100000)); // No stack overflow

In this example, each recursive call replaces the previous one, keeping the stack from
growing indefinitely.

56. Scenario: You want to ensure a callback function only runs once, even if called
multiple times.

Question: How would you implement a once function to limit execution?

Answer:
A once function ensures that a callback executes only the first time it is called, ignoring
subsequent calls. This is useful for initializing logic that should not run more than once.

For Example:

function once(fn) {
let executed = false;
return function (...args) {
if (!executed) {
executed = true;
fn(...args);
}
};
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
280

const logOnce = once((message) => console.log(message));


logOnce("This will run"); // Executes
logOnce("This will not run"); // Ignored

Here, the callback executes only once, even if called multiple times.

57. Scenario: You have nested promises and want to avoid callback hell.

Question: How would you flatten nested promises using async/await?

Answer:
Using async/await simplifies handling nested promises, improving code readability. Instead
of chaining .then() calls, you can await each promise sequentially.

For Example:

async function getData() {


const user = await fetch("https://api.example.com/user").then((res) =>
res.json());
const orders = await
fetch(`https://api.example.com/orders/${user.id}`).then((res) => res.json());
console.log({ user, orders });
}

getData();

This avoids callback hell and makes the code easier to maintain.

58. Scenario: You want to log errors and metadata from multiple asynchronous
tasks.

Question: How would you collect errors and logs efficiently from multiple promises?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
281

Answer:
Use Promise.allSettled() to collect results from multiple promises, including both
successes and errors.

For Example:

const tasks = [
Promise.resolve("Task 1 completed"),
Promise.reject("Task 2 failed"),
Promise.resolve("Task 3 completed")
];

Promise.allSettled(tasks).then((results) => {
results.forEach((result) => {
if (result.status === "fulfilled") {
console.log("Success:", result.value);
} else {
console.error("Error:", result.reason);
}
});
});

This ensures all results are logged, regardless of success or failure.

59. Scenario: You want to handle multiple errors differently based on their type.

Question: How would you differentiate between different error types in a catch block?

Answer:
Using instanceof, you can identify error types inside the catch block and handle them
accordingly.

For Example:

try {
JSON.parse("{ invalid json }"); // Throws SyntaxError
} catch (error) {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
282

if (error instanceof SyntaxError) {


console.error("Syntax Error:", error.message);
} else {
console.error("General Error:", error.message);
}
}

This approach ensures each error type is handled appropriately.

60. Scenario: You want to ensure that a critical operation retries automatically if it
fails.

Question: How would you implement an exponential backoff strategy for retries?

Answer:
Exponential backoff increases the delay between retries, helping to reduce server load. If a
request fails, it retries with increasing delay until the limit is reached.

For Example:

async function fetchWithRetry(url, retries = 3, delay = 1000) {


try {
const response = await fetch(url);
if (!response.ok) throw new Error("Failed to fetch");
return await response.json();
} catch (error) {
if (retries > 0) {
console.warn(`Retrying in ${delay}ms...`);
await new Promise((resolve) => setTimeout(resolve, delay));
return fetchWithRetry(url, retries - 1, delay * 2); // Exponential backoff
} else {
throw error;
}
}
}

fetchWithRetry("https://api.example.com/data").catch((error) =>
console.error("All retries failed:", error.message)

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
283

);

This strategy ensures retries are attempted gradually to avoid overwhelming the server.

61. Scenario: You want to conditionally retry an API call only for specific HTTP
status codes like 500 or 503.

Question: How would you implement conditional retries in JavaScript?

Answer:
Conditional retries ensure that you only retry requests when necessary, such as in the case of
server errors (e.g., 500, 503) but not for client errors like 404. This prevents wasting resources
on non-recoverable errors. With async/await, you can handle conditional logic smoothly
within the retry mechanism.

For Example:

async function fetchWithConditionalRetry(url, retries = 3) {


try {
let response = await fetch(url);
if (!response.ok) {
if ([500, 503].includes(response.status) && retries > 0) {
console.warn(`Retrying... (${retries} retries left)`);
return fetchWithConditionalRetry(url, retries - 1);
} else {
throw new Error(`Request failed with status ${response.status}`);
}
}
return await response.json();
} catch (error) {
console.error("Error:", error.message);
throw error;
}
}

In this code:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
284

● If the API returns status 500 or 503, it retries until the retries run out.
● For non-retryable errors (e.g., 404), the error is immediately thrown.

62. Scenario: You want to debug an issue where a deeply nested object property is
causing an error.

Question: How would you safely access deeply nested object properties?

Answer:
Accessing deeply nested properties can cause runtime errors if any intermediate property is
undefined. You can prevent this with optional chaining (?.) or use try-catch for robust
error handling.

For Example:

const user = { profile: { address: { city: "New York" } } };

try {
console.log(user.profile.address.city); // Valid access
console.log(user.profile.contact.email); // Error: Cannot read property 'email'
} catch (error) {
console.error("Error accessing property:", error.message);
}

// Using optional chaining


console.log(user.profile?.contact?.email ?? "Email not available");

● Optional chaining avoids errors by returning undefined if a property doesn’t exist.


● ?? (nullish coalescing) provides a default value if the result is null or undefined.

63. Scenario: You need to manage loading indicators during asynchronous


operations.

Question: How would you manage loading states with try-catch blocks?

Answer:
Using try-catch with loading indicators ensures that the indicator is shown while the

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
285

operation is in progress and hidden afterward. The finally block guarantees that the
indicator will be removed, even if an error occurs.

For Example:

async function fetchDataWithLoading(url) {


const loadingIndicator = document.getElementById("loading");
loadingIndicator.style.display = "block";

try {
const response = await fetch(url);
const data = await response.json();
console.log(data);
} catch (error) {
console.error("Error:", error.message);
} finally {
loadingIndicator.style.display = "none"; // Hides the indicator regardless
}
}

The loading indicator remains visible during the API call and is hidden whether the operation
succeeds or fails.

64. Scenario: You want to throttle function calls to limit API requests.

Question: How would you implement throttling in JavaScript?

Answer:
Throttling ensures a function runs at most once within a given time frame, preventing
excessive API requests. This is useful for scroll events or button clicks that might trigger
rapid, repeated operations.

For Example:

function throttle(fn, limit) {


let inThrottle;
return function (...args) {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
286

if (!inThrottle) {
fn(...args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}

const log = throttle((message) => console.log(message), 1000);

log("Hello"); // Executes
log("This will be ignored"); // Ignored
setTimeout(() => log("This will run"), 1100); // Runs after 1.1s

● Throttling ensures the function runs only once every 1 second, even if it is called
multiple times.

65. Scenario: You need to log errors along with stack traces for debugging.

Question: How would you use the Error object to log stack traces?

Answer:
The Error object in JavaScript provides a stack property that contains the sequence of
function calls leading to the error. This stack trace is essential for debugging complex issues.

For Example:

function a() {
b();
}

function b() {
c();
}

function c() {
throw new Error("Something went wrong");
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
287

try {
a();
} catch (error) {
console.error("Error:", error.message);
console.error("Stack trace:", error.stack);
}

The stack trace shows the entire call hierarchy, making it easier to trace where the error
originated.

66. Scenario: You want to stop requests after multiple failures to avoid
overwhelming a service.

Question: How would you create a circuit breaker in JavaScript?

Answer:
A circuit breaker stops making requests if failures exceed a threshold. After a cooldown
period, it resets to allow requests again. This avoids overloading services.

For Example:

class CircuitBreaker {
constructor(failureThreshold, resetTime) {
this.failures = 0;
this.failureThreshold = failureThreshold;
this.resetTime = resetTime;
this.state = "CLOSED";
}

async request(fn) {
if (this.state === "OPEN") {
throw new Error("Circuit breaker is open");
}

try {
const result = await fn();
this.failures = 0; // Reset on success

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
288

return result;
} catch (error) {
this.failures++;
if (this.failures >= this.failureThreshold) {
this.state = "OPEN";
setTimeout(() => (this.state = "CLOSED"), this.resetTime);
}
throw error;
}
}
}

const breaker = new CircuitBreaker(3, 5000);


breaker.request(() => fetch("https://api.example.com/data"))
.then((res) => console.log("Success"))
.catch((err) => console.error("Request failed:", err.message));

This circuit breaker prevents repeated failures from overloading the service.

67. Scenario: You need to cancel an ongoing fetch request.

Question: How would you implement a cancellation mechanism with AbortController?

Answer:
AbortController allows you to cancel asynchronous tasks like fetch requests.

For Example:

const controller = new AbortController();


const { signal } = controller;

async function fetchData(url) {


try {
const response = await fetch(url, { signal });
const data = await response.json();
console.log(data);
} catch (error) {
if (error.name === "AbortError") {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
289

console.warn("Fetch request cancelled");


} else {
console.error("Error:", error.message);
}
}
}

fetchData("https://api.example.com/data");

// Cancel the request


controller.abort();

This code safely cancels the request, preventing unwanted operations.

68. Scenario: You need to debounce a search input to prevent multiple requests.

Question: How would you implement a debounce function in JavaScript?

Answer:
Debouncing ensures that a function runs only after the user has stopped triggering it for a
set time.

For Example:

function debounce(fn, delay) {


let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => fn(...args), delay);
};
}

const search = debounce((query) => console.log("Searching for:", query), 300);

search("Ja"); // Ignored
search("Jav"); // Ignored
search("JavaScript"); // Executes after 300ms

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
290

This reduces unnecessary requests, only executing the function after the user finishes
typing.

69. Scenario: You want to implement retries with increasing delays.

Question: How would you combine promises with exponential backoff?

Answer:
Exponential backoff increases the delay between retries to avoid overwhelming the server.

For Example:

async function fetchWithBackoff(url, retries = 3, delay = 1000) {


try {
const response = await fetch(url);
if (!response.ok) throw new Error("Fetch failed");
return await response.json();
} catch (error) {
if (retries > 0) {
console.warn(`Retrying in ${delay}ms...`);
await new Promise((resolve) => setTimeout(resolve, delay));
return fetchWithBackoff(url, retries - 1, delay * 2);
} else {
throw error;
}
}
}

fetchWithBackoff("https://api.example.com/data").catch((error) =>
console.error("All retries failed:", error.message)
);

This technique reduces server load by increasing wait times between retries.

70. Scenario: You want to detect and handle memory leaks in your application.

Question: How would you debug memory leaks using browser developer tools?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
291

Answer:
Memory leaks occur when the app retains unused memory. Use the Memory tab in
Developer Tools to identify such leaks.

1. Open DevTools > Memory.


2. Take Heap Snapshots at intervals.
3. Compare snapshots to identify unreleased objects.

For Example:

const elements = [];


function createDiv() {
elements.push(document.createElement("div")); // Memory leak
}

setInterval(createDiv, 1000); // Memory grows indefinitely

Removing unnecessary references prevents memory leaks and ensures smooth


performance.

71.

Scenario: You want to execute multiple promises but stop when the first one completes,
regardless of success or failure.
Question: How would you use Promise.race() to handle this scenario?

Answer:
Promise.race() returns the result of the first promise to either resolve or reject, making it
useful when you only care about the earliest response.

For Example:

const p1 = new Promise((resolve) => setTimeout(resolve, 300, "First"));


const p2 = new Promise((resolve) => setTimeout(resolve, 500, "Second"));
const p3 = new Promise((_, reject) => setTimeout(reject, 200, "Rejected"));

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
292

Promise.race([p1, p2, p3])


.then((result) => console.log("Resolved:", result))
.catch((error) => console.error("Rejected:", error));

In this case, p3 wins the race by rejecting after 200ms, and the catch block handles the error.

72.

Scenario: You want to ensure that a specific block of code runs only after multiple
asynchronous operations complete successfully.
Question: How would you coordinate with Promise.all() to achieve this?

Answer:
Promise.all() runs multiple promises in parallel and waits until all of them are resolved. If
any promise fails, the entire operation fails.

For Example:

const fetchUser = fetch("https://api.example.com/user").then((res) => res.json());


const fetchOrders = fetch("https://api.example.com/orders").then((res) =>
res.json());

Promise.all([fetchUser, fetchOrders])
.then(([user, orders]) => {
console.log("User:", user);
console.log("Orders:", orders);
})
.catch((error) => console.error("Error fetching data:", error.message));

This code ensures both user and order data are fetched before proceeding.

73.

Scenario: You need to log the sequence of operations and group related logs.
Question: How would you use console.group() and console.groupEnd() for this purpose?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
293

Answer:
console.group() organizes logs into a collapsible group, improving readability in the
console.

For Example:

console.group("User Operations");
console.log("Fetching user...");
console.group("Order Operations");
console.log("Fetching orders...");
console.groupEnd(); // End Order group
console.groupEnd(); // End User Operations group

The logs appear nested, helping you visualize the operation flow.

74.

Scenario: You need to retry an operation only for network errors, not logic errors.
Question: How would you selectively retry based on error type?

Answer:
You can check the error type or message inside the catch block to retry selectively.

For Example:

async function fetchWithRetry(url, retries = 3) {


try {
const response = await fetch(url);
if (!response.ok) throw new Error("Logic error");
return await response.json();
} catch (error) {
if (error.message.includes("Network") && retries > 0) {
console.warn(`Retrying... (${retries} retries left)`);
return fetchWithRetry(url, retries - 1);
} else {
throw error;
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
294

}
}

This ensures network errors are retried, while logic errors are thrown immediately.

75.

Scenario: You need to clean up resources even if an operation fails.


Question: How would you use finally for cleanup operations?

Answer:
The finally block guarantees that cleanup operations run regardless of success or failure.

For Example:

async function fetchData(url) {


const connection = openConnection();
try {
const response = await fetch(url);
const data = await response.json();
console.log(data);
} catch (error) {
console.error("Error:", error.message);
} finally {
connection.close(); // Cleanup
}
}

This ensures resources are released even if the fetch fails.

76.

Scenario: You want to collect data from a stream but stop after the first error.
Question: How would you handle errors in a streaming operation?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
295

Answer:
Use ReadableStream with proper error handling to stop the stream after the first error.

For Example:

const stream = new ReadableStream({


start(controller) {
fetch("https://api.example.com/stream")
.then((response) => {
const reader = response.body.getReader();
return reader.read();
})
.then(({ done, value }) => {
if (done) controller.close();
else controller.enqueue(value);
})
.catch((error) => controller.error(error));
}
});

This code stops the stream if an error occurs during reading.

77.

Scenario: You need to handle a large number of tasks, but only a few should run
concurrently.
Question: How would you implement concurrency control with a queue?

Answer:
You can create a task queue to ensure only a limited number of tasks run simultaneously.

For Example:

class TaskQueue {
constructor(limit) {
this.limit = limit;
this.running = 0;

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
296

this.queue = [];
}

enqueue(task) {
this.queue.push(task);
this.runNext();
}

async runNext() {
if (this.running >= this.limit || this.queue.length === 0) return;

const task = this.queue.shift();


this.running++;
try {
await task();
} finally {
this.running--;
this.runNext();
}
}
}

const queue = new TaskQueue(2);

queue.enqueue(() => fetch("https://api.example.com/1"));


queue.enqueue(() => fetch("https://api.example.com/2"));
queue.enqueue(() => fetch("https://api.example.com/3"));

This ensures only two tasks run concurrently.

78.

Scenario: You need to implement a timeout for an API request.


Question: How would you implement a timeout mechanism with Promise.race()?

Answer:
Using Promise.race() allows you to implement a timeout by racing the API request against
a timeout promise.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
297

function timeout(ms) {
return new Promise((_, reject) =>
setTimeout(() => reject(new Error("Request timed out")), ms)
);
}

async function fetchWithTimeout(url, ms) {


return Promise.race([fetch(url), timeout(ms)]);
}

fetchWithTimeout("https://api.example.com/data", 5000)
.then((res) => console.log("Response received"))
.catch((error) => console.error(error.message));

If the API doesn’t respond within 5 seconds, the timeout promise rejects the operation.

79.

Scenario: You need to reduce the frequency of a function call without missing the last input.
Question: How would you implement a debounce function that ensures the last call runs?

Answer:
This debounce function ensures the last call always executes after the user stops triggering
it.

For Example:

function debounce(fn, delay) {


let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => fn(...args), delay);
};
}

const search = debounce((query) => console.log("Searching for:", query), 300);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
298

search("Ja");
search("Jav");
search("JavaScript"); // Executes after 300ms with the latest input

This ensures only the final input triggers the function.

80.

Scenario: You want to implement retry logic with a delay between attempts.
Question: How would you implement retry logic with delay and exponential backoff?

Answer:
Use exponential backoff to increase the delay between retries, reducing server load during
retries.

For Example:

async function fetchWithRetry(url, retries = 3, delay = 1000) {


try {
const response = await fetch(url);
if (!response.ok) throw new Error("Failed to fetch");
return await response.json();
} catch (error) {
if (retries > 0) {
console.warn(`Retrying in ${delay}ms...`);
await new Promise((resolve) => setTimeout(resolve, delay));
return fetchWithRetry(url, retries - 1, delay * 2);
} else {
throw error;
}
}
}

fetchWithRetry("https://api.example.com/data")
.catch((error) => console.error("All retries failed:", error.message));

This ensures each retry has a longer delay to avoid overloading the server.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
299

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
300

Chapter 6 : JavaScript ES6+ Features (Modern


JavaScript)

THEORETICAL QUESTIONS
1. What is destructuring in JavaScript, and how is it used with arrays?

Answer: Destructuring is a feature introduced in ES6 that simplifies the process of extracting
values from arrays or objects and assigning them to individual variables. In the case of arrays,
destructuring matches the array elements by position, meaning the first element in the array
will be assigned to the first variable in the destructuring syntax, the second to the second,
and so on.

This concise syntax helps reduce boilerplate code, especially when working with complex
data structures. If there are fewer variables than array elements, the remaining elements are
ignored. If there are more variables than elements, the extra variables are assigned
undefined.

For Example:

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


const [first, second] = numbers;

console.log(first); // 10
console.log(second); // 20

This assigns 10 to first and 20 to second. It’s particularly useful in functions that return
multiple values in an array.

2. How does object destructuring differ from array destructuring in


JavaScript?

Answer: Unlike array destructuring, object destructuring extracts properties from objects
based on their names, not their order. This makes it more flexible, especially when dealing
with complex objects. You can also provide default values and use aliases to rename
properties during destructuring.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
301

For Example:

const person = { name: "Alice", age: 30 };


const { name: userName, age } = person;

console.log(userName); // Alice
console.log(age); // 30

Here, the name property is extracted and assigned to a new variable userName. This flexibility
is useful when property names need to be mapped to more meaningful variable names in a
specific context.

3. What are the Spread and Rest operators in JavaScript?

Answer:

● Spread Operator (...): It spreads elements of an iterable (like arrays) or object


properties into a new structure.
● Rest Operator (...): It collects multiple elements or properties into an array or object.

For Example:

const arr1 = [1, 2];


const arr2 = [3, 4];
const newArray = [...arr1, ...arr2];

console.log(newArray); // [1, 2, 3, 4]

In this case, the Spread operator combines two arrays into one. The Rest operator, on the
other hand, allows gathering parameters into an array, making it helpful for working with
functions that take variable arguments.

For Example (Rest Operator):

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
302

function multiply(...nums) {
return nums.reduce((acc, num) => acc * num, 1);
}

console.log(multiply(2, 3, 4)); // 24

Here, all the passed arguments are collected into the nums array.

4. What are default parameters in JavaScript, and how do they work?

Answer: Default parameters let you define fallback values for function parameters. If the
function is called without those arguments, the default values are used instead. This feature
avoids undefined values and provides more readable and error-resistant code.

For Example:

function greet(name = "Guest") {


return `Hello, ${name}!`;
}

console.log(greet()); // Hello, Guest!


console.log(greet("Bob")); // Hello, Bob!

This makes it easier to handle cases where not all arguments are passed to a function.
Without default parameters, you’d have to write additional logic to handle such situations.

5. What are template literals in JavaScript, and how are they different from
regular strings?

Answer: Template literals, introduced in ES6, allow for cleaner and more readable string
interpolation. With regular strings, you would use concatenation, which can get messy with
complex expressions. Template literals use backticks and ${} for embedding variables or
expressions directly inside the string.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
303

const name = "John";


const message = `Hello, ${name}!`;

console.log(message); // Hello, John!

6. How does the for...of loop work in JavaScript?

Answer: The for...of loop simplifies iterating over iterable objects like arrays, strings, and
collections like Maps or Sets. It iterates directly over the values instead of indices, making it
cleaner and easier to understand.

For Example:

const colors = ['red', 'green', 'blue'];


for (const color of colors) {
console.log(color);
}

This loop logs each color directly without worrying about indexes. It’s particularly useful when
the index isn’t needed.

7. What is a generator function in JavaScript, and how do you define one?

Answer: A generator function is defined using function* and can yield multiple values. It
allows pausing and resuming execution, which makes it useful for lazy evaluation and
asynchronous programming.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
304

function* counter() {
yield 1;
yield 2;
yield 3;
}

const gen = counter();


console.log(gen.next().value); // 1
console.log(gen.next().value); // 2

Each call to next() resumes the generator from where it left off.

8. How do you use the export and import syntax in JavaScript?

Answer: The export and import syntax enables modular code, allowing you to split
functionality into different files and reuse it across your application. You can use both named
exports and default exports.

For Example (Named Export):

// math.js
export const PI = 3.14;
export function add(a, b) {
return a + b;
}

For Example (Import):

import { PI, add } from './math.js';


console.log(PI); // 3.14
console.log(add(2, 3)); // 5

This separation promotes cleaner code and easier maintenance.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
305

9. What are dynamic imports in JavaScript?

Answer: Dynamic imports allow you to load JavaScript modules only when needed,
improving the performance of large applications. This technique is often used in modern
frameworks like React for code-splitting.

For Example:

function loadMathModule() {
import('./math.js').then(module => {
console.log(module.add(5, 3)); // 8
});
}

loadMathModule();

This way, the module is loaded only when the function is called.

10. What is the Symbol data type in JavaScript, and why is it useful?

Answer: Symbols are unique, immutable values primarily used as keys in objects to avoid
property name conflicts. Even if two symbols have the same description, they are considered
different.

For Example:

const sym1 = Symbol('id');


const sym2 = Symbol('id');

console.log(sym1 === sym2); // false

Symbols ensure that object properties are unique, which is especially useful when
integrating libraries or frameworks to avoid accidental key conflicts.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
306

11. What is the difference between for...in and for...of loops in


JavaScript?

Answer:
The for...in and for...of loops are used for different purposes:

● for...in: Iterates over the keys or properties of an object or array. It works well for
objects but can also be used with arrays. However, it may return inherited properties,
which might not always be desirable.
● for...of: Iterates over the values of iterable objects like arrays, strings, or collections
(e.g., Sets, Maps). It is more appropriate for accessing array values directly without
needing the index.

For Example (for...in with an array):

const arr = [10, 20, 30];


for (const index in arr) {
console.log(index); // 0, 1, 2 (Indexes)
}

For Example (for...of with an array):

for (const value of arr) {


console.log(value); // 10, 20, 30 (Values)
}

Use for...of when working with array values and for...in when iterating through object
properties.

12. What is the purpose of the WeakSet in JavaScript?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
307

Answer:
WeakSet is similar to a regular Set but only allows objects as elements and holds weak
references to them. This means that if an object in a WeakSet has no other references, it will
be garbage-collected, improving memory management. This is useful when you want to
track objects without preventing them from being garbage-collected.

For Example:

let user = { name: "Alice" };


const weakSet = new WeakSet();
weakSet.add(user);

console.log(weakSet.has(user)); // true

user = null; // The object is now eligible for garbage collection

WeakSet cannot store primitive values and doesn’t support iteration since it’s designed for
temporary data storage.

13. What is a WeakMap in JavaScript, and how does it differ from a Map?

Answer:
WeakMap is similar to a Map but only allows objects as keys and holds weak references to
these keys. This ensures that if the object key is no longer referenced elsewhere, it will be
garbage-collected. This makes WeakMap useful for caching or storing metadata for objects.

For Example:

let obj = { id: 1 };


const weakMap = new WeakMap();
weakMap.set(obj, "Some Value");

console.log(weakMap.get(obj)); // "Some Value"

obj = null; // The key object can now be garbage-collected

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
308

In contrast, Map holds strong references to its keys, meaning the keys won't be garbage-
collected.

14. What is the difference between Set and WeakSet in JavaScript?

Answer:
The primary differences between Set and WeakSet are:

● Set can store any type of value, including primitives and objects, and allows iteration.
● WeakSet only stores objects and does not allow iteration.

For Example (Set):

const set = new Set([1, 2, 3]);


set.add(4);
console.log(set); // Set { 1, 2, 3, 4 }

For Example (WeakSet):

let obj = { data: 100 };


const weakSet = new WeakSet();
weakSet.add(obj);

obj = null; // The object is eligible for garbage collection

Use WeakSet for memory-efficient, temporary object storage.

15. What is the purpose of the Map object in JavaScript?

Answer:
A Map allows you to store key-value pairs with any data type as a key. It preserves the
insertion order and provides methods like set(), get(), delete(), and has().

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
309

const map = new Map();


map.set("name", "Alice");
map.set(1, "One");

console.log(map.get("name")); // Alice
console.log(map.has(1)); // true

Maps are preferable over objects when you need complex key types or insertion order.

16. What are ES modules, and how are they different from CommonJS
modules?

Answer:
ES Modules (ESM) and CommonJS (CJS) are two different module systems in JavaScript.
ESM uses import and export for modular code, while CJS uses require() and
module.exports. ESM modules are statically analyzed, allowing better optimizations by
bundlers, and are supported natively in browsers.

For Example (ESM):

// math.js
export const PI = 3.14;
export function add(a, b) {
return a + b;
}

// app.js
import { PI, add } from './math.js';
console.log(PI); // 3.14

CJS is typically used in Node.js, but ESM is becoming the standard for web applications.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
310

17. What are iterators in JavaScript?

Answer:
An iterator is an object that provides a next() method to access elements one at a time from
a collection. Each call to next() returns an object with two properties:

● value: The current element.


● done: A boolean indicating whether the iteration is complete.

For Example:

const iterator = [1, 2, 3][Symbol.iterator]();


console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

Iterators are useful for handling custom iteration logic.

18. What is the difference between null and undefined in JavaScript?

Answer:

● undefined: A variable that is declared but not assigned any value.


● null: An intentional assignment to indicate "no value."

For Example:

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

let b = null;
console.log(b); // null

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
311

undefined usually indicates a missing value, while null is used to represent an intentionally
empty value.

19. What is the use of async and await in JavaScript?

Answer:
The async/await syntax simplifies working with asynchronous code. An async function
always returns a promise, and await pauses the function execution until the promise is
resolved or rejected.

For Example:

async function fetchData() {


const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
}

fetchData();

This code makes the fetch request easier to read compared to using .then() chains.

20. What is the difference between var, let, and const?

Answer:

● var: Function-scoped, can be redeclared, and hoisted.


● let: Block-scoped, can’t be redeclared in the same scope, but can be reassigned.
● const: Block-scoped, can neither be redeclared nor reassigned after initialization.

For Example:

var a = 1;
let b = 2;

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
312

const c = 3;

a = 4; // Valid
b = 5; // Valid
// c = 6; // Error: Assignment to constant variable

Using let and const helps prevent bugs caused by variable hoisting and scoping issues.

21. What is the difference between synchronous and asynchronous


JavaScript? How does the event loop manage asynchronous operations?

Answer:

● Synchronous operations execute sequentially. Each operation must complete before


the next one begins, blocking further code execution if an operation takes time (e.g.,
fetching data from a server).
● Asynchronous operations allow other code to execute while waiting for a task to
complete (like a network request). This makes JavaScript non-blocking and
responsive.

The event loop manages asynchronous tasks by moving completed tasks from the task
queue or microtask queue to the call stack only when the call stack is empty. This ensures
that asynchronous code doesn’t block the main thread.

For Example:

console.log("Start");

setTimeout(() => console.log("Timeout"), 1000);


console.log("End");

The setTimeout callback is delayed until the synchronous code finishes executing.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
313

22. How do promises work in JavaScript? What are Promise.all and


Promise.race used for?

Answer:
A promise represents the result of an asynchronous operation that can be in three states:
pending, fulfilled, or rejected. Promises simplify chaining asynchronous operations using
.then() and .catch().

● Promise.all: Runs multiple promises concurrently and resolves only when all
succeed or rejects if any fail.
● Promise.race: Resolves or rejects as soon as the first promise settles, regardless of the
result.

For Example:

const p1 = new Promise(resolve => setTimeout(() => resolve("First"), 100));


const p2 = new Promise(resolve => setTimeout(() => resolve("Second"), 200));

Promise.all([p1, p2]).then(values => console.log(values)); // ["First", "Second"]


Promise.race([p1, p2]).then(value => console.log(value)); // "First"

23. What is a closure in JavaScript? How does it work?

Answer:
A closure occurs when a function retains access to variables from its outer scope, even after
the outer function has executed. This enables data encapsulation and private variables.

For Example:

function outerFunction(outerVariable) {
return function innerFunction(innerVariable) {
console.log(`Outer: ${outerVariable}, Inner: ${innerVariable}`);
};
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
314

const closure = outerFunction("outside");


closure("inside"); // Outer: outside, Inner: inside

The inner function remembers the value of outerVariable even after outerFunction has
finished executing.

24. What is the difference between deep and shallow copy in JavaScript?

Answer:

● Shallow Copy: Only copies the top-level properties, leaving nested objects as
references. Changes to nested objects affect both copies.
● Deep Copy: Creates a new object, recursively copying all properties and nested
objects, making them independent.

For Example (Shallow Copy):

const obj = { a: 1, b: { c: 2 } };
const shallowCopy = { ...obj };
shallowCopy.b.c = 42;
console.log(obj.b.c); // 42 (Affects original)

For Example (Deep Copy):

const deepCopy = JSON.parse(JSON.stringify(obj));


deepCopy.b.c = 100;
console.log(obj.b.c); // 42 (Original remains unchanged)

25. How does JavaScript handle memory management with garbage


collection?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
315

Answer:
JavaScript uses automatic garbage collection through the mark-and-sweep algorithm. The
mark phase identifies reachable objects, and the sweep phase removes those that are
unreachable, freeing memory.

● Reference Counting: If no references exist to an object, it becomes eligible for


garbage collection.
● Mark-and-Sweep: Periodically, JavaScript marks reachable objects. Objects left
unmarked are removed.

For Example:

let obj1 = { name: "Alice" };


let obj2 = obj1; // obj2 references the same object
obj1 = null; // The object is not collected because obj2 still references it

26. What are higher-order functions in JavaScript? Provide an example.

Answer:
A higher-order function is a function that takes another function as an argument or returns
a function. They are a key concept in functional programming, enabling flexibility and
reusable logic.

For Example:

function higherOrderFunction(callback) {
callback("Hello from callback!");
}

higherOrderFunction((message) => console.log(message)); // Hello from callback!

Higher-order functions are used in common methods like map(), filter(), and reduce().

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
316

27. What is the purpose of the this keyword in JavaScript? How does arrow
function behavior differ with this?

Answer:
The this keyword refers to the context from which a function is called. The value of this
changes depending on the calling object.

● Arrow functions don’t have their own this. They inherit it from their lexical scope (the
surrounding code).

For Example:

const obj = {
name: "Alice",
regularFunction: function () {
console.log(this.name); // Alice
},
arrowFunction: () => {
console.log(this.name); // undefined (no lexical `this` in global scope)
}
};

obj.regularFunction();
obj.arrowFunction();

28. What is event delegation in JavaScript?

Answer:
Event delegation takes advantage of event bubbling, allowing a parent element to handle
events for its children, even if they are added dynamically. This avoids attaching individual
listeners to each child.

For Example:

document.getElementById("parent").addEventListener("click", (event) => {


if (event.target.tagName === "BUTTON") {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
317

console.log("Button clicked:", event.target.textContent);


}
});

This example ensures the parent handles clicks on any buttons inside it, even if the buttons
are added later.

29. What is currying in JavaScript? Provide an example.

Answer:
Currying is a technique where a function with multiple parameters is transformed into a
series of functions, each taking a single parameter. This allows partial application, where
some arguments can be pre-filled.

For Example:

function add(a) {
return function (b) {
return a + b;
};
}

const addFive = add(5);


console.log(addFive(3)); // 8

Here, add(5) returns a new function that adds 5 to any given input.

30. What are the different ways to create objects in JavaScript?

Answer:
JavaScript provides several ways to create objects:

Object Literal:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
318

const obj = { name: "Alice" };


A quick way to define objects with properties and values.
Constructor Function:

function Person(name) {
this.name = name;
}
const alice = new Person("Alice");
This approach is used to create multiple instances.
Object.create():

const proto = { greet: function () { console.log("Hello!"); } };


const obj = Object.create(proto);
obj.greet(); // Hello!
Creates a new object with a specified prototype.
Class Syntax:

class Person {
constructor(name) {
this.name = name;
}
}
const alice = new Person("Alice");

31. What is the call(), apply(), and bind() method in JavaScript? How are
they different?

Answer:
These methods allow us to explicitly set the this context when invoking a function. This is
useful when a function needs to operate within a different context or when reusing methods
across objects.

● call(): Invokes a function with a specific this value and arguments passed
individually.
● apply(): Similar to call(), but arguments are passed as an array.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
319

● bind(): Returns a new function with this bound to the specified object, which can
be invoked later.

For Example:

const person = { name: "Alice" };

function greet(greeting, punctuation) {


console.log(`${greeting}, ${this.name}${punctuation}`);
}

greet.call(person, "Hello", "!"); // Hello, Alice!


greet.apply(person, ["Hi", "."]); // Hi, Alice.
const boundGreet = greet.bind(person, "Hey");
boundGreet("?"); // Hey, Alice?

call() and apply() execute immediately, while bind() creates a new function that can be
invoked later.

32. What is prototypal inheritance in JavaScript? How does it differ from


classical inheritance?

Answer:
In prototypal inheritance, objects inherit directly from other objects. Every JavaScript object
has a [[Prototype]] (or __proto__), pointing to another object from which it inherits
properties and methods.

In classical inheritance (like in Java), classes are blueprints used to create objects.
JavaScript’s prototypes make inheritance more dynamic.

For Example:

const parent = { greet: function() { console.log("Hello!"); } };


const child = Object.create(parent);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
320

child.greet(); // Hello!

Here, child inherits the greet method from parent.

33. What are JavaScript decorators?

Answer:
Decorators are functions that modify the behavior of classes or methods. They add
additional behavior or metadata. Although still experimental, they are useful in frameworks
like Angular.

For Example:

function readonly(target, key, descriptor) {


descriptor.writable = false;
return descriptor;
}

class Example {
@readonly
method() {
console.log("This method is readonly.");
}
}

The @readonly decorator ensures the method cannot be modified.

34. How does async/await work under the hood?

Answer:
async/await is syntactic sugar over promises. It makes asynchronous code look synchronous.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
321

● async: Declares a function that returns a promise.


● await: Pauses execution until the promise resolves.

For Example:

async function getData() {


const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
}
getData();

Internally, the engine creates a promise chain behind the scenes.

35. What is memoization, and how does it improve performance?

Answer:
Memoization stores the results of function calls based on input arguments. If the same
inputs are encountered again, the cached result is returned.

For Example:

function memoize(fn) {
const cache = {};
return function (...args) {
const key = JSON.stringify(args);
if (cache[key]) return cache[key];
const result = fn(...args);
cache[key] = result;
return result;
};
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
322

const add = memoize((a, b) => a + b);


console.log(add(2, 3)); // 5
console.log(add(2, 3)); // 5 (from cache)

This avoids redundant computations, improving performance.

36. How does JavaScript handle shadowing of variables?

Answer:
Variable shadowing occurs when a variable declared in a child scope uses the same name
as one in the outer scope. The inner variable overrides access to the outer one within its
scope.

For Example:

let a = 1;
function test() {
let a = 2; // Shadows the outer variable
console.log(a); // 2
}
test();
console.log(a); // 1

In the function, a refers to the inner scope variable. Outside the function, it refers to the outer
scope.

37. What are Web Workers in JavaScript, and how do they work?

Answer:
Web Workers allow running JavaScript code in background threads to prevent blocking the
main UI thread. They are especially useful for long-running tasks.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
323

For Example:

// worker.js
self.onmessage = function (e) {
const result = e.data * 2;
postMessage(result);
};

// main.js
const worker = new Worker('worker.js');
worker.postMessage(5); // Send data to worker
worker.onmessage = function (e) {
console.log(e.data); // 10
};

Web Workers communicate via postMessage() and onmessage.

38. What is the module pattern in JavaScript, and why is it useful?

Answer:
The module pattern creates private scope and exposes public methods. It relies on closures
to keep variables private while exposing certain functions.

For Example:

const Counter = (function () {


let count = 0;
return {
increment() {
count++;
console.log(count);
},
getCount() {
return count;

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
324

}
};
})();

Counter.increment(); // 1
Counter.increment(); // 2
console.log(Counter.getCount()); // 2

This ensures that count is not accessible directly from outside.

39. What are dynamic imports in JavaScript, and when should you use
them?

Answer:
Dynamic imports allow loading modules at runtime instead of loading everything at the
start. This is helpful for lazy loading parts of the application.

For Example:

async function loadModule() {


const { add } = await import('./math.js');
console.log(add(2, 3)); // 5
}
loadModule();

Dynamic imports improve performance by splitting code and loading it only when needed.

40. What is tail call optimization, and how does it work in JavaScript?

Answer:
Tail call optimization (TCO) is an optimization technique where the last function call in a

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
325

function is optimized to reuse the current stack frame. This prevents stack overflow in
recursive functions.

For Example:

function factorial(n, acc = 1) {


if (n === 0) return acc;
return factorial(n - 1, n * acc); // Tail call
}
console.log(factorial(5)); // 120

TCO reduces memory usage by reusing stack frames, making recursion more efficient.

SCENARIO QUESTIONS
41. How can you use array destructuring to assign multiple values to
variables in JavaScript?

Answer:
Array destructuring allows you to extract values from an array and assign them to individual
variables. This feature simplifies accessing array elements by reducing code redundancy. It
follows the positional order of elements in the array, assigning the first value to the first
variable, the second to the second, and so on.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
326

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


const [first, second, third] = fruits;

console.log(first); // Apple
console.log(second); // Banana
console.log(third); // Mango

If the array has fewer elements than variables, the unassigned variables will be undefined.
You can also skip elements by leaving gaps between commas, such as [ , second ].

42. How can you use object destructuring to access specific properties of
an object?

Answer:
Object destructuring allows you to extract properties from objects by matching property
names. It provides an easy way to assign properties to variables with the same names or use
aliases if needed. If the property doesn’t exist in the object, the variable is assigned
undefined.

For Example:

const user = { name: "Alice", age: 30, country: "USA" };


const { name: userName, age, country } = user;

console.log(userName); // Alice
console.log(age); // 30
console.log(country); // USA

Here, the name property is assigned to a variable called userName through aliasing.

43. How can the spread operator be used to combine multiple arrays?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
327

Answer:
The spread operator (...) expands the elements of an iterable, such as arrays, into individual
elements. This allows you to merge multiple arrays or clone an existing array into a new
one. It’s a convenient way to avoid modifying the original arrays.

For Example:

const arr1 = [1, 2, 3];


const arr2 = [4, 5, 6];
const mergedArray = [...arr1, ...arr2];

console.log(mergedArray); // [1, 2, 3, 4, 5, 6]

You can also insert additional elements within the new array using spread syntax.

44. How can the rest operator be used to collect multiple function
arguments?

Answer:
The rest operator (...) collects multiple function arguments into an array. This is helpful for
functions that accept a variable number of arguments, ensuring they can be accessed
consistently as an array.

For Example:

function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}

console.log(sum(1, 2, 3, 4)); // 10

The ...numbers parameter gathers all function arguments into the numbers array.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
328

45. How do default parameters work in JavaScript?

Answer:
Default parameters allow you to assign default values to function parameters if no
arguments are provided during the function call. This helps avoid errors when optional
parameters are missing.

For Example:

function greet(name = "Guest") {


console.log(`Hello, ${name}!`);
}

greet(); // Hello, Guest!


greet("Alice"); // Hello, Alice!

Here, "Guest" is used as the default value if no argument is passed.

46. How do template literals work in JavaScript?

Answer:
Template literals allow for string interpolation and multi-line strings using backticks (`)
instead of quotes. You can embed variables and expressions using ${} within the string,
making code more readable.

For Example:

const name = "Alice";


const greeting = `Hello, ${name}!`;

console.log(greeting); // Hello, Alice!

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
329

With template literals, you can also create multi-line strings without special characters.

47. How does the for...of loop work in JavaScript?

Answer:
The for...of loop simplifies iteration over iterable objects like arrays, strings, and sets. It
directly accesses the values of the iterable, avoiding the need to manage indexes.

For Example:

const numbers = [10, 20, 30];


for (const num of numbers) {
console.log(num);
}
// Output:
// 10
// 20
// 30

for...of is especially useful when you don't need the index of elements.

48. How can you create and use generators in JavaScript?

Answer:
Generators are functions that allow pausing and resuming execution. They are defined using
the function* syntax and yield values on demand using the yield keyword.

For Example:

function* generator() {
yield 1;
yield 2;

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
330

yield 3;
}

const gen = generator();


console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3

Each call to next() retrieves the next value from the generator.

49. How do export and import work in ES6 modules?

Answer:
ES6 modules allow splitting JavaScript code across multiple files. You can use the export
keyword to expose variables, functions, or classes, and the import keyword to include them
in other files.

For Example (math.js):

export const PI = 3.14;


export function add(a, b) {
return a + b;
}

This modular approach makes code easier to maintain and reuse.

50. How can Symbols be used in JavaScript?

Answer:
Symbols are unique and immutable primitive values that can be used as object property

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
331

keys. Even if two symbols have the same description, they are not considered equal. This
ensures that property names are unique, preventing conflicts.

For Example:

const sym1 = Symbol('id');


const sym2 = Symbol('id');

console.log(sym1 === sym2); // false

const user = {
[sym1]: 1,
name: "Alice"
};

console.log(user[sym1]); // 1

Symbols are often used to create hidden properties in objects that won't collide with other
property names.

51. How can you use nested destructuring to extract values from deeply
nested objects?

Answer:
In JavaScript, nested destructuring lets you extract properties from nested objects into
variables directly, reducing repetitive code. You specify nested properties within matching
curly braces {}. Default values can be assigned if a property might be undefined.

This is useful when working with APIs that return complex JSON data.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
332

const person = {
name: "Alice",
address: {
city: "New York",
zip: 10001
}
};

const { name, address: { city, zip = "N/A" } } = person;

console.log(name); // Alice
console.log(city); // New York
console.log(zip); // 10001

● The name is destructured into a variable.


● The nested city and zip are extracted from the address object.
● If the zip property were missing, the default value "N/A" would be used.

52. How can the spread operator be used to flatten arrays in JavaScript?

Answer:
The spread operator (...) expands elements from arrays or objects into new structures.
When flattening arrays, it can spread elements from sub-arrays into the parent array,
effectively reducing nesting.

However, the spread operator only flattens one level deep. For multi-level nested arrays,
Array.flat() should be used.

For Example:

const nestedArray = [1, 2, [3, 4], [5, [6, 7]]];


const flatArray = [].concat(...nestedArray);

console.log(flatArray); // [1, 2, 3, 4, 5, [6, 7]]

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
333

53. How can default parameters ensure optional function arguments?

Answer:
Default parameters ensure that a function works even if certain arguments are not provided.
Instead of returning undefined when an argument is missing, the default value is used.

For Example:

function multiply(a, b = 1) {
return a * b;
}

console.log(multiply(5)); // 5 (b defaults to 1)
console.log(multiply(5, 2)); // 10

Here:

● If only one argument is passed, b takes the default value of 1.


● If both arguments are provided, the function uses the given values.

54. How do template literals simplify string concatenation?

Answer:
Template literals (introduced in ES6) make it easy to embed variables or expressions inside
strings using ${}. They also support multi-line strings without requiring special characters
like \n. This simplifies code compared to traditional string concatenation.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
334

const firstName = "Alice";


const lastName = "Johnson";
const fullName = `${firstName} ${lastName}`;

console.log(fullName); // Alice Johnson

55. How does the for...in loop help in iterating over object properties?

Answer:
The for...in loop iterates over the keys of an object (or indices in arrays). This loop is useful
when you need both the property names and values from an object.

For Example:

const user = { name: "Alice", age: 30 };


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

● If the object has inherited properties, the for...in loop will iterate over them as well.
To avoid this, use:

for (const key in user) {


if (user.hasOwnProperty(key)) {
console.log(`${key}: ${user[key]}`);
}
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
335

56. How can generators be used for lazy evaluation?

Answer:
Generators allow pausing and resuming function execution using the yield keyword. This
makes them ideal for lazy evaluation, where values are produced only when needed. This
approach is memory efficient when dealing with large or infinite sequences.

For Example:

function* idGenerator() {
let id = 1;
while (true) {
yield id++;
}
}

const gen = idGenerator();


console.log(gen.next().value); // 1
console.log(gen.next().value); // 2

Each call to next() retrieves the next value, pausing the function at the yield keyword until
the next call.

57. How do named imports work in ES6 modules?

Answer:
Named imports allow importing only specific exports from a module. This improves
performance by loading only what is needed. If you export multiple things from a

module, you can import only the relevant ones.


For Example (math.js):

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
336

export const PI = 3.14;


export function add(a, b) {
return a + b;
}

For Example (app.js):

import { add } from './math.js';


console.log(add(2, 3)); // 5

Here, only the add function is imported, leaving the rest of the module untouched.

58. How do dynamic imports improve code splitting?

Answer:
Dynamic imports allow loading modules at runtime using import(). This enables code
splitting, where only the required parts of the code are loaded on demand, improving
application performance.

For Example:

async function loadMathModule() {


const { add } = await import('./math.js');
console.log(add(2, 3)); // 5
}

loadMathModule();

In this example, the math.js module is imported only when the function is called, reducing
the initial load time of the application.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
337

59. How do symbols ensure unique object property keys?

Answer:
A Symbol is a unique and immutable value used as a property key. Even if two symbols have
the same description, they are not considered equal. This ensures property name
uniqueness and avoids naming conflicts.

For Example:

const sym1 = Symbol('id');


const sym2 = Symbol('id');

const user = { [sym1]: 1, name: "Alice" };


console.log(user[sym1]); // 1
console.log(sym1 === sym2); // false

Using symbols ensures that object properties won’t collide, even if other parts of the code
use similar names.

60. How do sets help in managing unique values?

Answer:
A Set stores only unique values. If you try to add duplicate elements, they are ignored. This
makes Set useful for filtering unique values from arrays.

For Example:

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


const uniqueNumbers = new Set(numbers);

console.log([...uniqueNumbers]); // [1, 2, 3, 4, 5]

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
338

The Set automatically removes duplicate values, and the spread operator (...) converts the
Set back into an array.

61. How can you use deep destructuring with default values in JavaScript?

Answer:
Deep destructuring allows you to extract values from nested objects at multiple levels. If
certain properties are missing, default values can be provided to prevent undefined from
causing runtime issues. This feature is especially useful when dealing with data structures
like API responses.

For Example:

const user = {
name: "Alice",
address: {
city: "New York",
zip: 10001
}
};

const {
address: { city, zip = "N/A", state = "Unknown" }
} = user;

console.log(city); // New York


console.log(zip); // 10001
console.log(state); // Unknown

Explanation:

● zip defaults to "N/A" if the property is missing.


● state defaults to "Unknown" since it is not present in the object. This structure
ensures robust access to deeply nested data.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
339

62. How can you use a generator function to generate Fibonacci numbers?

Answer:
The Fibonacci sequence is a series of numbers where each number is the sum of the
previous two. Using a generator, we can yield these numbers on demand, making it
memory efficient. Instead of calculating all numbers at once, a generator yields each number
only when needed.

For Example:

function* fibonacci() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}

const fib = fibonacci();


console.log(fib.next().value); // 0
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2

Explanation:
Each call to next() advances the generator to the next number in the sequence. The
generator only computes the current value, making it ideal for infinite sequences.

63. How can memoization improve the performance of recursive


functions?

Answer:
Memoization stores the results of previous function calls, preventing redundant calculations.
This is particularly effective for recursive functions that would otherwise recompute the

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
340

same values multiple times. For example, calculating large Fibonacci numbers with recursion
alone would be inefficient without memoization.

For Example:

const memo = {};


function fibonacci(n) {
if (n <= 1) return n;
if (memo[n]) return memo[n];
memo[n] = fibonacci(n - 1) + fibonacci(n - 2);
return memo[n];
}

console.log(fibonacci(10)); // 55

Explanation:
The function stores computed values in memo, so repeated calls with the same input don’t
perform unnecessary recalculations.

64. How can closures be used to create private members in JavaScript


classes?

Answer:
In JavaScript, closures allow functions to access variables from their outer scope even after
the outer function has executed. This feature can be leveraged to simulate private members
in a class-like structure, ensuring that internal data can’t be directly accessed.

For Example:

function Person(name) {
let _age = 0; // Private variable

this.getName = function() {
return name;

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
341

};

this.getAge = function() {
return _age;
};

this.setAge = function(age) {
if (age > 0) _age = age;
};
}

const alice = new Person("Alice");


alice.setAge(30);
console.log(alice.getAge()); // 30

Explanation:
The variable _age is not directly accessible from outside the function. It can only be modified
or accessed through the provided methods.

65. How can dynamic imports be used to conditionally load JavaScript


modules?

Answer:
Dynamic imports load modules only when required, helping with code splitting and
improving performance. This technique is useful in large applications where not all modules
are needed at the same time.

For Example:

async function loadComponent(componentName) {


if (componentName === "math") {
const { add } = await import('./math.js');
console.log(add(2, 3)); // 5
}
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
342

loadComponent("math"); // Module is loaded only when needed

Explanation:
Here, the math.js module is imported only when the user selects the "math" component,
reducing the initial load time of the application.

66. How can symbols prevent naming collisions in JavaScript?

Answer:
Symbols provide unique identifiers for object properties, ensuring that even if different
libraries or parts of the code use the same name, the properties won’t conflict. This makes
symbols particularly useful in library integration.

For Example:

const LIBRARY_SYMBOL = Symbol("myLibrary");

const obj = {
[LIBRARY_SYMBOL]: "Library-specific value",
name: "Alice"
};

console.log(obj[LIBRARY_SYMBOL]); // Library-specific value

Explanation:
Even if another library uses "myLibrary" as a key, it won’t conflict because symbols are
unique.

67. How does WeakMap help in associating metadata with objects?

Answer:
A WeakMap stores key-value pairs where the keys are objects. If the object is no longer

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
343

referenced elsewhere, it will be garbage-collected along with its metadata. This ensures
efficient memory usage without leaks.

For Example:

const metaData = new WeakMap();

let user = { name: "Alice" };


metaData.set(user, { role: "admin" });

console.log(metaData.get(user)); // { role: "admin" }

user = null; // Now the object can be garbage-collected

Explanation:
The WeakMap entry is removed automatically when the object is garbage-collected.

68. How can for...of be used to iterate over key-value pairs in a Map?

Answer:
The for...of loop allows you to iterate directly over the key-value pairs of a Map. Each
iteration returns an array containing the key and value.

For Example:

const map = new Map([


["name", "Alice"],
["age", 30]
]);

for (const [key, value] of map) {


console.log(`${key}: ${value}`);
}
// Output:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
344

// name: Alice
// age: 30

Explanation:
Unlike plain objects, Map preserves insertion order and supports direct iteration.

69. How can JavaScript classes be used to implement the Singleton


pattern?

Answer:
The Singleton pattern ensures that only one instance of a class exists throughout the
application. This is useful for shared state or configuration management.

For Example:

class Singleton {
constructor() {
if (Singleton.instance) return Singleton.instance;
this.data = "Shared State";
Singleton.instance = this;
}
}

const instance1 = new Singleton();


const instance2 = new Singleton();
console.log(instance1 === instance2); // true

Explanation:
Here, both instance1 and instance2 refer to the same instance, ensuring that state
remains consistent.

70. How does WeakSet help prevent memory leaks in JavaScript?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
345

Answer:
A WeakSet holds weak references to objects, allowing them to be garbage-collected when
no longer needed. This prevents memory leaks when tracking temporary objects.

For Example:

let user = { name: "Alice" };


const weakSet = new WeakSet();
weakSet.add(user);

console.log(weakSet.has(user)); // true

user = null; // Now the object can be garbage-collected

Explanation:
The object inside the WeakSet will be automatically removed from memory when no other
references to it exist.

71. How does Promise.all() work, and when should you use it?

Answer:
Promise.all() is used to run multiple asynchronous operations in parallel. It returns a
single promise that resolves when all the input promises resolve, or rejects if any one of the
promises rejects. This method is useful when you need to wait for multiple tasks (like
fetching different data sources) to complete before proceeding.

For Example:

const fetchUser = () => new Promise((resolve) => setTimeout(() => resolve("User"),


1000));
const fetchPosts = () => new Promise((resolve) => setTimeout(() =>
resolve("Posts"), 500));

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
346

Promise.all([fetchUser(), fetchPosts()])
.then(([user, posts]) => console.log(`Fetched: ${user}, ${posts}`))
.catch((error) => console.error("Error fetching data:", error));

Explanation:

● Both promises start executing in parallel.


● The result contains the values of all resolved promises in an array.
● If any promise fails, the entire operation fails with an error.

72. How does Promise.race() differ from Promise.all()?

Answer:
Promise.race() resolves or rejects as soon as the first promise settles (whether it resolves
or rejects). This is useful in scenarios where you care about the fastest result, such as setting
timeouts or choosing the fastest server response.

For Example:

const slowPromise = new Promise((resolve) => setTimeout(() => resolve("Slow"),


2000));
const fastPromise = new Promise((resolve) => setTimeout(() => resolve("Fast"),
500));

Promise.race([slowPromise, fastPromise])
.then((result) => console.log(result)) // Fast
.catch((error) => console.error(error));

Explanation:

● Since fastPromise resolves first, Promise.race() completes with "Fast".


● This is useful for selecting the fastest response from multiple asynchronous
operations.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
347

73. How can you use JavaScript getters and setters in a class?

Answer:
Getters and setters allow you to encapsulate access to class properties, providing controlled
access and validation. Getters retrieve the value of a property, and setters update it, ensuring
valid data.

For Example:

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

get name() {
return this._name;
}

set name(newName) {
if (newName.length > 0) this._name = newName;
else console.error("Name cannot be empty");
}
}

const user = new User("Alice");


console.log(user.name); // Alice
user.name = "Bob"; // Valid update
console.log(user.name); // Bob
user.name = ""; // Invalid update (error)

Explanation:

● Getters and setters allow validation and side effects, such as logging changes, to
occur during access or modification of properties.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
348

74. How can async/await simplify asynchronous code?

Answer:
async/await provides a cleaner syntax for asynchronous operations, avoiding deeply nested
.then() callbacks. An async function always returns a promise, and await pauses the
execution until the promise resolves.

For Example:

async function fetchData() {


try {
const response = await fetch("https://api.example.com/data");
const data = await response.json();
console.log(data);
} catch (error) {
console.error("Error fetching data:", error);
}
}

fetchData();

Explanation:

● await waits for the promise to resolve before moving to the next line.
● This syntax makes asynchronous code easier to read and maintain.

75. How can default and named exports be used together in ES6 modules?

Answer:
ES6 modules allow you to combine named and default exports. A default export represents
the primary functionality of the module, while named exports provide additional utilities or
constants.

For Example (math.js):

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
349

export default function multiply(a, b) {


return a * b;
}

export const PI = 3.14;

For Example (app.js):

import multiply, { PI } from './math.js';


console.log(multiply(2, 3)); // 6
console.log(PI); // 3.14

Explanation:

● Default exports can be imported with any name.


● Named exports must match their declared names.

76. How can method chaining be implemented in JavaScript?

Answer:
Method chaining allows calling multiple methods on the same object sequentially. Each
method must return this to enable the next method call.

For Example:

class Calculator {
constructor(value = 0) {
this.value = value;
}

add(n) {
this.value += n;
return this;
}

subtract(n) {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
350

this.value -= n;
return this;
}

multiply(n) {
this.value *= n;
return this;
}

getResult() {
console.log(this.value);
return this.value;
}
}

const calc = new Calculator();


calc.add(5).subtract(2).multiply(3).getResult(); // 9

Explanation:

● Each method returns the same object, enabling continuous chaining.

77. How can the ?? operator help with default values?

Answer:
The nullish coalescing operator (??) returns the right-hand operand only if the left-hand
operand is null or undefined. This is useful for assigning meaningful default values
without triggering on falsy values like 0 or "".

For Example:

const name = null ?? "Guest";


console.log(name); // Guest

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
351

const age = 0 ?? 18;


console.log(age); // 0 (since 0 is not null or undefined)

Explanation:

● ?? only checks for null or undefined, making it more accurate than || for default
assignments.

78. How can Set be used to remove duplicate elements from an array?

Answer:
A Set stores only unique values. By converting an array into a Set and back into an array, you
can easily remove duplicates.

For Example:

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


const uniqueNumbers = [...new Set(numbers)];

console.log(uniqueNumbers); // [1, 2, 3, 4, 5]

Explanation:

● The Set removes duplicates, and the spread operator converts the Set back into an
array.

79. How can clearTimeout and clearInterval be used to manage timers?

Answer:
clearTimeout and clearInterval are used to cancel time-based operations started with
setTimeout or setInterval.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
352

const timeoutId = setTimeout(() => console.log("This will not run"), 1000);


clearTimeout(timeoutId); // Cancels the timeout

const intervalId = setInterval(() => console.log("Repeating"), 500);


setTimeout(() => clearInterval(intervalId), 2000); // Stops after 2 seconds

Explanation:

● clearTimeout stops a one-time operation before it executes.


● clearInterval stops a repeating operation.

80. How can Map store objects as keys?

Answer:
A Map can use any data type, including objects, as keys. This makes it ideal for associating
metadata or tracking state with objects.

For Example:

const map = new Map();


const objKey = { id: 1 };

map.set(objKey, "Object Value");


console.log(map.get(objKey)); // Object Value

console.log(map.has(objKey)); // true

Explanation:

● Unlike regular objects, Map maintains key-value pairs where keys can be objects. This
makes it more powerful for complex data management.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
353

Chapter 7 : Advanced JavaScript Concepts

THEORETICAL QUESTIONS
1. What is currying in JavaScript?

Answer:
Currying is a functional programming technique in JavaScript that transforms a function
with multiple parameters into a sequence of functions, each taking a single parameter.
Instead of providing all the arguments at once, you provide them one by one across multiple
function calls. This makes functions more flexible and reusable. It allows developers to create
more specialized functions from generic ones, enabling a clear separation of concerns and
avoiding repetitive code.

Currying is particularly useful in event handling, configuration-based operations, and


working with libraries like Redux, where partial applications are common.

For Example:

function curryAdd(a) {
return function (b) {
return function (c) {
return a + b + c;
};
};
}

const add = curryAdd(1)(2)(3);


console.log(add); // Output: 6

In the example above, curryAdd takes one argument at a time, but the final result is
calculated when all three functions are invoked.

2. What is partial application in JavaScript?

Answer:
Partial application refers to a function that takes a few arguments initially and returns

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
354

another function that can take the rest of the arguments later. It’s like pre-filling some
arguments of a function so it can be reused efficiently. This pattern enhances code
reusability, especially when some arguments remain constant across multiple calls.

The primary difference between currying and partial application is that currying transforms
functions to accept one argument at a time, whereas partial application uses the original
function but locks in a subset of the parameters.

For Example:

function multiply(a, b, c) {
return a * b * c;
}

const partialMultiply = multiply.bind(null, 2); // '2' is fixed


console.log(partialMultiply(3, 4)); // Output: 24

Here, partialMultiply binds the first argument to 2 and returns a new function that
expects the remaining two arguments.

3. What is memoization, and how is it used?

Answer:
Memoization is an optimization technique that improves performance by caching the results
of expensive function calls. If the function is called with the same input multiple times, the
cached result is returned instead of recalculating the value. Memoization is helpful in
recursive algorithms such as Fibonacci, where intermediate results are reused.

This technique reduces redundant calculations and improves efficiency, especially when
dealing with time-consuming or resource-intensive functions.

For Example:

const memoize = (fn) => {


const cache = {};
return (...args) => {
const key = JSON.stringify(args);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
355

if (cache[key]) {
return cache[key];
}
const result = fn(...args);
cache[key] = result;
return result;
};
};

const fibonacci = memoize((n) => {


if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
});

console.log(fibonacci(10)); // Output: 55

In this example, memoize caches the result of Fibonacci calculations to avoid redundant
function calls.

4. What is throttling in JavaScript?

Answer:
Throttling ensures that a function is called at most once within a specified time interval, even
if it is triggered multiple times. It is commonly used in scenarios where events like scrolling,
resizing, or button clicks need to be optimized. With throttling, the function execution is
spaced out, avoiding unnecessary overhead.

For Example:

function throttle(func, limit) {


let inThrottle;
return function (...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
356

};
}

const handleScroll = throttle(() => {


console.log('Scroll event triggered!');
}, 1000);
window.addEventListener('scroll', handleScroll);

In this example, the handleScroll function is throttled to ensure it only runs once per
second, even if the scroll event fires continuously.

5. How does debouncing differ from throttling?

Answer:
Debouncing ensures that a function is only called after a specified period of inactivity. Unlike
throttling, which spaces out function calls over time, debouncing triggers the function only
once, after the event stops firing. It is commonly used in search fields to prevent unnecessary
API calls with every keystroke.

For Example:

function debounce(func, delay) {


let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), delay);
};
}

const handleInput = debounce((event) => {


console.log('Search query:', event.target.value);
}, 500);

document.getElementById('searchInput').addEventListener('input', handleInput);

This example uses debouncing to ensure that the search query is logged only after the user
stops typing for 500ms.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
357

6. What is a Singleton pattern in JavaScript?

Answer:
The Singleton pattern restricts the instantiation of a class to a single instance throughout the
application. It ensures that only one object is created, which can be shared among different
parts of the application. This pattern is often used for managing shared resources such as a
database connection or a logging service.

For Example:

const Singleton = (function () {


let instance;
function createInstance() {
return { name: 'Singleton Instance' };
}
return {
getInstance: function () {
if (!instance) {
instance = createInstance();
}
return instance;
},
};
})();

const obj1 = Singleton.getInstance();


const obj2 = Singleton.getInstance();
console.log(obj1 === obj2); // Output: true

Both obj1 and obj2 refer to the same instance, demonstrating the Singleton pattern.

7. What is the Observer pattern in JavaScript?

Answer:
The Observer pattern allows objects (observers) to be notified of state changes in another
object (subject). It is commonly used in event-driven programming, where multiple
components need to react to certain events or state changes.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
358

For Example:

class Subject {
constructor() {
this.observers = [];
}
subscribe(observer) {
this.observers.push(observer);
}
notify(data) {
this.observers.forEach((observer) => observer.update(data));
}
}

class Observer {
update(data) {
console.log('Received:', data);
}
}

const subject = new Subject();


const observer1 = new Observer();
const observer2 = new Observer();
subject.subscribe(observer1);
subject.subscribe(observer2);
subject.notify('Hello Observers!');

This example demonstrates how observers receive updates from the subject when it sends
notifications.

8. What is the Factory pattern in JavaScript?

Answer:
The Factory pattern provides a way to create objects without specifying the exact class of the
object being created. It is useful for scenarios where multiple types of objects share a
common interface but differ in their behavior or properties.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
359

class Car {
constructor() {
this.type = 'Car';
}
}

class Bike {
constructor() {
this.type = 'Bike';
}
}

function VehicleFactory(vehicleType) {
if (vehicleType === 'car') return new Car();
if (vehicleType === 'bike') return new Bike();
}

const myCar = VehicleFactory('car');


console.log(myCar.type); // Output: Car

The factory method abstracts the creation logic, making the code more maintainable.

9. What is the Module pattern in JavaScript?

Answer:
The Module pattern helps encapsulate variables and methods within a function, exposing
only the parts needed through a public API. It prevents global namespace pollution and
organizes code into smaller, manageable units.

For Example:

const CounterModule = (function () {


let counter = 0;
return {
increment: function () {
counter++;
},

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
360

getCount: function () {
return counter;
},
};
})();

CounterModule.increment();
console.log(CounterModule.getCount()); // Output: 1

This example encapsulates the counter variable, preventing direct access from outside.

10. What are pure functions in JavaScript?

Answer:
A pure function always produces the same output for the same input and has no side effects
(like modifying variables outside its scope). Pure functions are central to functional
programming because they make code easier to reason about and test.

For Example:

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

console.log(add(2, 3)); // Output: 5


console.log(add(2, 3)); // Output: 5 (Always the same for the same inputs)

The add function is pure because it always returns the same result for the same inputs and
does not modify any external state.

11. What is immutability in JavaScript, and why is it important?

Answer:
Immutability means once a variable or object is created, it cannot be changed. Instead of

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
361

modifying an existing object or array, we create a new one with the required changes. This
concept is central to functional programming, where maintaining the state without side
effects is essential. Immutability ensures that no other part of the code accidentally changes
the data, reducing bugs and making code easier to debug.

For Example:

const arr = [1, 2, 3];

// Instead of modifying the original array, create a new one


const newArr = [...arr, 4];

console.log(arr); // Output: [1, 2, 3]


console.log(newArr); // Output: [1, 2, 3, 4]

Here, instead of changing arr, we use the spread operator to create a new array newArr. This
prevents unintended modifications to the original data.

12. What is function composition in JavaScript?

Answer:
Function composition is a technique to combine multiple functions into one. It means
passing the result of one function as input to another, chaining them together. This approach
makes code more readable and easier to test since each function handles one task.

For Example:

const add = (x) => x + 2;


const multiply = (x) => x * 3;

const composedFunction = (x) => multiply(add(x));

console.log(composedFunction(2)); // Output: 12

Here, composedFunction adds 2 to the input and then multiplies the result by 3, showing
how functions are combined.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
362

13. What is the purpose of the map method in JavaScript?

Answer:
The map method transforms an array by applying a function to each of its elements, returning
a new array without modifying the original. It’s useful when you need to modify or format
data elements in an array.

For Example:

const numbers = [1, 2, 3];


const doubled = numbers.map((num) => num * 2);

console.log(doubled); // Output: [2, 4, 6]

Here, map creates a new array where each element is doubled, demonstrating transformation
using the map method.

14. How does the filter method work in JavaScript?

Answer:
The filter method extracts elements from an array that meet a specific condition, returning
a new array. It’s useful when you need to remove unwanted data or pick specific elements
from an array.

For Example:

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


const evenNumbers = numbers.filter((num) => num % 2 === 0);

console.log(evenNumbers); // Output: [2, 4]

In this example, the filter method returns a new array containing only even numbers.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
363

15. What is the reduce method in JavaScript?

Answer:
The reduce method processes an array and reduces it to a single value using a reducer
function. It’s commonly used for summing values or accumulating data into a single result.

For Example:

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


const sum = numbers.reduce((acc, curr) => acc + curr, 0);

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

Here, reduce sums all elements in the array, starting with an initial value of 0.

16. What is the difference between map and forEach in JavaScript?

Answer:
map and forEach both iterate over an array, but their purposes differ. map returns a new array
with transformed elements, while forEach performs an operation for each element but
doesn’t return anything.

For Example:

const numbers = [1, 2, 3];

numbers.forEach((num) => console.log(num));


// Output: 1 2 3 (logged individually)

const squared = numbers.map((num) => num * num);


console.log(squared); // Output: [1, 4, 9]

Here, forEach logs elements individually, while map creates a new array with squared values.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
364

17. What is the purpose of the some method in JavaScript?

Answer:
The some method checks if at least one element in an array matches a condition. It returns
true if any element satisfies the condition; otherwise, it returns false.

For Example:

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


const hasEven = numbers.some((num) => num % 2 === 0);

console.log(hasEven); // Output: true

Here, some checks if the array contains any even numbers, and returns true as soon as it
finds one.

18. What is the every method in JavaScript?

Answer:
The every method verifies whether all elements in an array meet a specific condition. It
returns true only if every element passes the test; otherwise, it returns false.

For Example:

const numbers = [2, 4, 6];


const allEven = numbers.every((num) => num % 2 === 0);

console.log(allEven); // Output: true

In this example, every confirms that all numbers in the array are even.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
365

19. What are higher-order functions in JavaScript?

Answer:
A higher-order function is a function that takes another function as an argument or returns a
function. These functions are powerful because they allow abstracting and reusing behavior
across multiple parts of a program.

For Example:

function higherOrderFunction(func) {
return function (name) {
return func(name);
};
}

const greet = higherOrderFunction((name) => `Hello, ${name}!`);


console.log(greet('Alice')); // Output: Hello, Alice!

Here, the higher-order function returns another function that can greet a user by name.

20. What are closures in JavaScript?

Answer:
A closure is a function that remembers the variables from its outer scope, even after the
outer function has finished executing. Closures allow functions to access and maintain state
between function calls, making them useful for encapsulating private variables and building
factories.

For Example:

function outerFunction(outerVariable) {
return function innerFunction(innerVariable) {
console.log(`Outer: ${outerVariable}, Inner: ${innerVariable}`);
};
}

const closureFunc = outerFunction('outside');

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
366

closureFunc('inside'); // Output: Outer: outside, Inner: inside

In this example, innerFunction retains access to outerVariable even after outerFunction


has completed, demonstrating the concept of closures.

21. What is the difference between synchronous and asynchronous


JavaScript?

Answer:
In synchronous programming, code runs sequentially, meaning each line waits for the
previous one to finish before executing. This behavior is easy to understand but can lead to
performance bottlenecks if a time-consuming operation (like reading files or fetching data)
blocks the execution of the remaining code.

In asynchronous programming, long-running operations are executed in the background.


The remaining code continues without waiting, and the asynchronous task notifies when it is
done (using callbacks, promises, or async/await).

For Example:

console.log('Start');

setTimeout(() => {
console.log('Async Task');
}, 1000);

console.log('End');
// Output: Start, End, Async Task (after 1 second)

Here, setTimeout delays execution, but since it is asynchronous, the next


console.log('End') executes immediately.

22. How does JavaScript’s event loop work?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
367

Answer:
JavaScript is single-threaded, meaning it can execute one task at a time. However, with the
help of the event loop, JavaScript can handle asynchronous operations like timers and
network requests. The call stack holds synchronous code, while asynchronous callbacks are
queued in the callback queue or microtask queue. When the call stack is empty, the event
loop picks tasks from these queues to execute.

For Example:

console.log('Start');

setTimeout(() => {
console.log('Timeout Callback');
}, 0);

console.log('End');
// Output: Start, End, Timeout Callback

Although the timer is set to 0ms, it gets queued and only runs after the synchronous code
finishes.

23. What are JavaScript Promises, and how do they work?

Answer:
A Promise represents the result of an asynchronous operation. It can either be resolved
(fulfilled) or rejected (failed). Promises allow writing asynchronous code without the
complexity of deeply nested callbacks.

For Example:

const fetchData = new Promise((resolve, reject) => {


setTimeout(() => resolve('Data fetched!'), 1000);
});

fetchData
.then((result) => console.log(result)) // Output: Data fetched!
.catch((error) => console.error(error));

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
368

The fetchData promise resolves after 1 second, triggering the .then() method to display the
result.

24. What is the purpose of async and await in JavaScript?

Answer:
async and await simplify working with promises by making asynchronous code look
synchronous. An async function returns a Promise, and await pauses the function execution
until the Promise resolves. This approach reduces callback complexity and makes code more
readable.

For Example:

async function getData() {


const result = await new Promise((resolve) =>
setTimeout(() => resolve('Fetched Data'), 1000)
);
console.log(result);
}

getData(); // Output: Fetched Data (after 1 second)

Here, await makes the function wait for the promise to resolve before proceeding to the next
line.

25. What are JavaScript generators, and how are they used?

Answer:
Generators are special functions that can be paused and resumed. They allow lazy
evaluation, meaning they generate values one at a time instead of all at once. This can be
useful when working with large datasets.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
369

function* numberGenerator() {
yield 1;
yield 2;
yield 3;
}

const generator = numberGenerator();


console.log(generator.next().value); // Output: 1
console.log(generator.next().value); // Output: 2
console.log(generator.next().value); // Output: 3

Each yield pauses the function and returns a value. Calling next() resumes the generator
from where it left off.

26. What is the difference between shallow copy and deep copy in
JavaScript?

Answer:
A shallow copy copies the top-level properties, but nested objects still reference the original.
A deep copy, however, recursively copies all properties, including nested objects, ensuring
complete independence from the original data.

For Example:

const original = { a: 1, b: { c: 2 } };
const shallowCopy = { ...original };
shallowCopy.b.c = 42;

console.log(original.b.c); // Output: 42 (shallow copy issue)

Here, both original and shallowCopy reference the same nested object, leading to
unintentional changes.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
370

27. How does JavaScript handle memory leaks, and what are common
causes?

Answer:
JavaScript uses garbage collection to manage memory by automatically removing objects
that are no longer referenced. However, memory leaks occur when references to unused
objects persist, preventing them from being collected.

Common causes:

1. Global variables that stay in memory throughout the app.


2. Closures holding onto variables longer than necessary.
3. Event listeners not removed properly.

For Example:

let leak = [];


function createLeak() {
leak.push(new Array(1000000).fill('*')); // Large memory usage
}
createLeak(); // Memory isn't released, causing a leak

28. What is the concept of tail recursion in JavaScript?

Answer:
Tail recursion occurs when the recursive call is the last operation in the function. Some
JavaScript engines optimize tail-recursive functions to avoid stack overflow, reusing the
same stack frame for each call.

For Example:

function factorial(n, acc = 1) {


if (n === 0) return acc;
return factorial(n - 1, n * acc); // Tail recursion
}

console.log(factorial(5)); // Output: 120

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
371

Here, the recursive call to factorial is the last operation, making it tail-recursive.

29. What is the significance of the Symbol type in JavaScript?

Answer:
Symbol is a unique and immutable primitive value introduced in ES6. It is often used to
create unique property keys to prevent naming conflicts, especially in large codebases or
when extending objects.

For Example:

const sym1 = Symbol('key');


const sym2 = Symbol('key');

console.log(sym1 === sym2); // Output: false

Even though both symbols have the same description, they are unique.

30. What are JavaScript Proxies, and how do they work?

Answer:
A Proxy allows developers to intercept and customize operations performed on an object,
such as getting or setting properties. This is useful for validation, logging, or data binding in
frameworks like Vue.js.

For Example:

const target = { name: 'Alice' };


const handler = {
get: (obj, prop) => {
console.log(`Getting ${prop}`);
return obj[prop];
},
set: (obj, prop, value) => {
console.log(`Setting ${prop} to ${value}`);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
372

obj[prop] = value;
return true;
},
};

const proxy = new Proxy(target, handler);


console.log(proxy.name); // Output: Getting name, Alice
proxy.age = 25; // Output: Setting age to 25

This Proxy logs every get and set operation, providing a way to monitor and control access to
the object.

31. What is the purpose of the Reflect API in JavaScript, and how is it
used?

Answer:
The Reflect API provides built-in methods to operate on objects (e.g., accessing, setting, or
defining properties) in a more predictable way. Unlike regular property access or assignment
(e.g., obj.name), Reflect methods return true or false to indicate success or failure. It helps
developers create robust code by standardizing object operations and is often used with
Proxies to implement default behaviors.

For Example:

const obj = { name: 'Alice' };

// Using Reflect to get and set properties


console.log(Reflect.get(obj, 'name')); // Output: Alice
Reflect.set(obj, 'age', 25);
console.log(obj); // Output: { name: 'Alice', age: 25 }

The Reflect methods provide cleaner alternatives to regular operations, especially when
working with Proxies or advanced object manipulation.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
373

32. How do JavaScript WeakMap and WeakSet differ from Map and Set?

Answer:

● WeakMap and WeakSet store only objects (no primitives) and weakly reference
them, allowing these objects to be garbage collected when no other references exist.
● Map and Set, on the other hand, strongly reference the objects, preventing their
garbage collection.

For Example:

let obj = { name: 'Alice' };


const weakMap = new WeakMap();
weakMap.set(obj, 'some value');

obj = null; // Object is now eligible for garbage collection

In the example, the object will be garbage collected once it is no longer referenced, as
WeakMap holds only weak references, preventing memory leaks.

33. What is the difference between call, apply, and bind methods in
JavaScript?

Answer:

● call: Invokes a function with a specific this context and individual arguments.
● apply: Works like call but accepts an array of arguments.
● bind: Returns a new function with the specified this context and optional
arguments, but doesn't execute it immediately.

For Example:

const obj = { name: 'Alice' };

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

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
374

greet.call(obj, 'Hello'); // Output: Hello, Alice


greet.apply(obj, ['Hi']); // Output: Hi, Alice

const boundGreet = greet.bind(obj, 'Hey');


boundGreet(); // Output: Hey, Alice

These methods allow developers to explicitly control the this value of a function, which is
crucial in complex JavaScript applications.

34. How does Promise.all differ from Promise.race?

Answer:

● Promise.all resolves only when all promises in the array are fulfilled (or rejects if any
of them fails).
● Promise.race resolves as soon as the first promise in the array settles (either resolved
or rejected).

For Example:

const promise1 = new Promise((resolve) => setTimeout(() => resolve('P1'), 1000));


const promise2 = new Promise((resolve) => setTimeout(() => resolve('P2'), 500));

Promise.all([promise1, promise2]).then(console.log); // Output: ['P1', 'P2']


Promise.race([promise1, promise2]).then(console.log); // Output: P2

Promise.all waits for all promises, but Promise.race returns the result of the first one to
settle.

35. What are mixins in JavaScript, and how are they implemented?

Answer:
Mixins are a way to share behavior across multiple classes or objects without inheritance.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
375

JavaScript doesn’t support multiple inheritance, so mixins allow code reuse by copying
methods and properties into objects or classes.

For Example:

const sayHelloMixin = {
sayHello() {
console.log(`Hello, ${this.name}`);
},
};

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

Object.assign(User.prototype, sayHelloMixin);

const user = new User('Alice');


user.sayHello(); // Output: Hello, Alice

Mixins enable behavior sharing across unrelated objects by assigning properties dynamically.

36. What is function throttling, and why is it useful?

Answer:
Throttling ensures that a function is called at most once in a specified period, no matter how
often the triggering event occurs. This is especially useful for scroll or resize events, where
frequent updates can degrade performance.

For Example:

function throttle(func, limit) {


let inThrottle;
return function (...args) {
if (!inThrottle) {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
376

func.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}

const log = throttle(() => console.log('Throttled!'), 1000);


window.addEventListener('scroll', log);

In the example, the log function is called only once per second, no matter how often the
scroll event fires.

37. What is memoization, and how does it optimize performance?

Answer:
Memoization stores the results of expensive function calls. If the function is called with the
same inputs again, the cached result is returned. This improves performance by avoiding
repeated calculations.

For Example:

const memoize = (fn) => {


const cache = {};
return (...args) => {
const key = JSON.stringify(args);
if (cache[key]) return cache[key];
const result = fn(...args);
cache[key] = result;
return result;
};
};

const factorial = memoize((n) => (n <= 1 ? 1 : n * factorial(n - 1)));

console.log(factorial(5)); // Output: 120

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
377

Here, factorial uses memoization to avoid redundant calculations.

38. What is the difference between == and === in JavaScript?

Answer:

● == compares values with type coercion, meaning it converts the operands to the
same type before comparing.
● === performs strict comparison, ensuring that both the type and value are the same.

For Example:

console.log(1 == '1'); // Output: true (type coercion)


console.log(1 === '1'); // Output: false (strict comparison)

Using === is preferred as it avoids unintended behavior caused by type coercion.

39. How does destructuring work in JavaScript?

Answer:
Destructuring allows extracting values from arrays or objects into variables directly, reducing
boilerplate code and improving readability.

For Example:

const user = { name: 'Alice', age: 25 };


const { name, age } = user;

console.log(name); // Output: Alice


console.log(age); // Output: 25

In the example, name and age are extracted from the user object and assigned to variables
with the same names.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
378

40. What is dynamic import, and how is it used in JavaScript?

Answer:
Dynamic import allows importing JavaScript modules at runtime, rather than at the
beginning of the script. It is useful for code splitting, as it loads modules only when needed,
improving initial load time.

For Example:

async function loadModule() {


const { default: module } = await import('./myModule.js');
module();
}

loadModule();

Here, myModule.js is imported only when loadModule is called, ensuring that the module is
loaded on demand, reducing the initial bundle size.

SCENARIO QUESTIONS
41. Scenario: You need to create a function that adds three numbers, but
you want to partially apply some of its parameters for reuse.

Question: How can you use partial application to simplify this process?

Answer:
Partial application allows you to lock in some arguments of a function, creating a new
function that only requires the remaining arguments. This is particularly helpful when certain
values remain constant across multiple calls. In JavaScript, this is commonly implemented
using closures or the bind() method. By partially applying parameters, you avoid repetitive
code, improving both efficiency and readability.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
379

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

// Partially applying the first argument


const addFive = add.bind(null, 5);

console.log(addFive(3, 2)); // Output: 10


console.log(addFive(1, 1)); // Output: 7

Here, the addFive function always adds 5 as the first parameter, requiring only the remaining
two inputs during function calls.

42. Scenario: You have a recursive function that performs expensive


calculations. You want to optimize it so it doesn’t recalculate results
unnecessarily.

Question: How can memoization be used to improve the performance of this


recursive function?

Answer:
Memoization improves performance by caching the results of previous function calls and
returning the cached value when the same inputs are encountered. This is useful in recursive
algorithms like Fibonacci or factorial, where the same calculations may be repeated multiple
times. Memoization reduces time complexity by storing and reusing results, avoiding
redundant work.

For Example:

const memoize = (fn) => {


const cache = {};
return (...args) => {
const key = JSON.stringify(args);
if (cache[key]) return cache[key];
const result = fn(...args);
cache[key] = result;

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
380

return result;
};
};

const fibonacci = memoize((n) => {


if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
});

console.log(fibonacci(10)); // Output: 55

In this example, memoization saves intermediate results, avoiding redundant calculations for
the Fibonacci sequence.

43. Scenario: You want to ensure a button click event handler is not
triggered excessively when the user clicks repeatedly.

Question: How can you use throttling to control the frequency of function calls?

Answer:
Throttling ensures a function runs at most once within a specified interval, even if it is
triggered multiple times. This is useful for optimizing performance in events that can fire
continuously, like scrolling, resizing, or button clicks. Throttling prevents system overload by
spacing out function executions.

For Example:

function throttle(func, limit) {


let inThrottle;
return function (...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
381

const handleClick = throttle(() => console.log('Button clicked!'), 2000);

document.getElementById('myButton').addEventListener('click', handleClick);

Here, the handleClick function executes only once every two seconds, even if the button is
clicked repeatedly.

44. Scenario: You need a central object to manage subscriptions and notify
subscribers when an event occurs.

Question: How can you implement the Observer pattern in JavaScript?

Answer:
The Observer pattern allows a subject to maintain a list of observers and notify them of state
changes. It is useful in event-driven architectures, where multiple components react to
specific events. This pattern ensures a decoupled design, where the subject and observers
are loosely connected.

For Example:

class Subject {
constructor() {
this.observers = [];
}
subscribe(observer) {
this.observers.push(observer);
}
notify(data) {
this.observers.forEach((observer) => observer.update(data));
}
}

class Observer {
update(data) {
console.log('Received:', data);
}
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
382

const subject = new Subject();


const observer1 = new Observer();
const observer2 = new Observer();

subject.subscribe(observer1);
subject.subscribe(observer2);

subject.notify('Event triggered');
// Output: Received: Event triggered
// Received: Event triggered

In this example, both observers receive notifications whenever the subject triggers an event.

45. Scenario: You need to ensure that only one instance of a logging service
exists across the application.

Question: How can you use the Singleton pattern to achieve this?

Answer:
The Singleton pattern ensures that only one instance of a class exists and provides a global
point of access to it. This pattern is useful for services like logging, configuration
management, or database connections, where creating multiple instances is unnecessary or
inefficient.

For Example:

class Logger {
constructor() {
if (Logger.instance) {
return Logger.instance;
}
this.logs = [];
Logger.instance = this;
}

log(message) {
this.logs.push(message);
console.log('Log:', message);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
383

}
}

const logger1 = new Logger();


const logger2 = new Logger();

logger1.log('First message');
logger2.log('Second message');

console.log(logger1 === logger2); // Output: true

Both logger1 and logger2 refer to the same instance, ensuring consistent logging across
the application.

46. Scenario: You need a function that processes a list of user objects, but
only the active ones should be included in the result.

Question: How can you use the filter method to implement this?

Answer:
The filter method creates a new array containing only elements that meet a specific
condition. This method is ideal for extracting subsets of data based on certain criteria.

For Example:

const users = [
{ name: 'Alice', active: true },
{ name: 'Bob', active: false },
{ name: 'Charlie', active: true }
];

const activeUsers = users.filter(user => user.active);

console.log(activeUsers);
// Output: [{ name: 'Alice', active: true }, { name: 'Charlie', active: true }]

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
384

In this example, only users marked as active are included in the result.

47. Scenario: You want to encapsulate a counter so that its value can only
be incremented through a provided method.

Question: How can the Module pattern be used to implement this?

Answer:
The Module pattern allows encapsulation of data and methods, exposing only what is
necessary through a public API. This pattern helps control access to variables and provides a
clean, maintainable structure.

For Example:

const CounterModule = (function () {


let count = 0;
return {
increment: function () {
count++;
},
getCount: function () {
return count;
},
};
})();

CounterModule.increment();
CounterModule.increment();
console.log(CounterModule.getCount()); // Output: 2

In this example, the count variable is private and can only be modified using the increment
method.

48. Scenario: You need to combine two functions that manipulate strings
to create a single transformation.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
385

Question: How can function composition be used for this purpose?

Answer:
Function composition allows you to combine smaller functions into a single function that
performs a sequence of operations. It helps keep code modular and easy to maintain.

For Example:

const toUpperCase = (str) => str.toUpperCase();


const exclaim = (str) => `${str}!`;

const shout = (str) => exclaim(toUpperCase(str));


console.log(shout('hello')); // Output: HELLO!

Here, the shout function applies both toUpperCase and exclaim transformations.

49. Scenario: You need to perform mathematical operations on an array,


such as summing all its elements.

Question: How can the reduce method be used to implement this?

Answer:
The reduce method accumulates values from an array into a single result. It is useful for
operations like summing, finding averages, or computing products.

For Example:

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

const sum = numbers.reduce((acc, curr) => acc + curr, 0);

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

In this example, reduce sums the array elements starting with an initial value of 0.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
386

50. Scenario: You need a function that converts an array of names into
uppercase.

Question: How can the map method be used for this transformation?

Answer:
The map method creates a new array by applying a function to each element of the original
array. It is useful for transforming data without altering the original collection.

For Example:

const names = ['alice', 'bob', 'charlie'];

const upperCaseNames = names.map(name => name.toUpperCase());

console.log(upperCaseNames); // Output: ['ALICE', 'BOB', 'CHARLIE']

Here, the map method converts each name to uppercase, creating a new array with the
transformed values.

51. Scenario: You need to ensure that a function only executes after a user
stops typing in a search field.

Question: How can debouncing be used to implement this behavior?

Answer:
Debouncing ensures a function is invoked only after a specified delay has passed since the
last event. It is commonly used in search input fields to prevent excessive API calls or
computations as the user types. If the event (like input) is triggered again before the delay
ends, the timer resets. This technique improves performance and reduces server load.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
387

function debounce(func, delay) {


let timeout;
return function (...args) {
clearTimeout(timeout); // Clear the previous timeout
timeout = setTimeout(() => func.apply(this, args), delay);
};
}

const handleSearch = debounce((event) => {


console.log('Search query:', event.target.value);
}, 500);

document.getElementById('searchInput').addEventListener('input', handleSearch);

Here, the search function triggers only after the user stops typing for 500ms, ensuring only
necessary API calls are made.

52. Scenario: You want to create a unique identifier for object properties
that won’t conflict with other keys.

Question: How can the Symbol type be used to achieve this?

Answer:
A Symbol is a unique and immutable data type used primarily for creating non-colliding
property keys. Even if two symbols share the same description, they are treated as distinct.
This prevents accidental property name conflicts, which is useful when adding metadata or
private fields to objects.

For Example:

const sym1 = Symbol('uniqueKey');


const obj = {
[sym1]: 'Value associated with sym1'
};

console.log(obj[sym1]); // Output: Value associated with sym1


console.log(Object.keys(obj)); // Output: [] (Symbol keys are not enumerable)

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
388

Symbols help keep properties hidden from standard iteration (like Object.keys) and avoid
key collisions.

53. Scenario: You want to manage asynchronous operations and ensure


that all of them are completed before proceeding.

Question: How can Promise.all be used to handle multiple asynchronous tasks?

Answer:
Promise.all waits for all promises to either resolve or reject. It returns a single promise that
resolves with an array of results when all promises succeed or rejects with the reason if any
promise fails. This is useful for parallel execution of asynchronous operations.

For Example:

const promise1 = new Promise((resolve) => setTimeout(() => resolve('P1'), 1000));


const promise2 = new Promise((resolve) => setTimeout(() => resolve('P2'), 500));

Promise.all([promise1, promise2]).then((results) => {


console.log(results); // Output: ['P1', 'P2']
});

Here, both promises are executed concurrently, and their results are combined into an array
when both succeed.

54. Scenario: You need to run asynchronous tasks, but only care about the
result of the first one to complete.

Question: How can Promise.race be used to handle this situation?

Answer:
Promise.race returns a promise that resolves or rejects as soon as the first promise settles
(either resolved or rejected). This is useful when you need the fastest result among several
operations.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
389

const promise1 = new Promise((resolve) => setTimeout(() => resolve('P1'), 1000));


const promise2 = new Promise((resolve) => setTimeout(() => resolve('P2'), 500));

Promise.race([promise1, promise2]).then((result) => {


console.log(result); // Output: P2
});

Here, promise2 resolves first, so its result is returned, even though promise1 is still pending.

55. Scenario: You want to safely access deeply nested properties in an


object without causing errors if they don’t exist.

Question: How can optional chaining be used to achieve this?

Answer:
Optional chaining (?.) allows you to access nested properties safely, without worrying about
whether each property in the chain exists. If a property is undefined or null, the expression
short-circuits and returns undefined instead of throwing an error. This makes the code more
robust.

For Example:

const user = { profile: { name: 'Alice' } };


console.log(user.profile?.name); // Output: Alice
console.log(user.address?.city); // Output: undefined (No error)

Here, accessing user.address?.city doesn’t throw an error even though address is


undefined.

56. Scenario: You need to split a large function into smaller modules and
load them dynamically based on user actions.

Question: How can dynamic imports be used to achieve this?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
390

Answer:
Dynamic imports allow JavaScript to load modules on demand instead of during the initial
load. This improves performance by loading only the necessary code at runtime, enabling
code-splitting for better application performance.

For Example:

async function loadModule() {


const { default: module } = await import('./myModule.js');
module();
}

document.getElementById('loadButton').addEventListener('click', loadModule);

Here, the module is loaded only when the button is clicked, reducing the initial load time and
improving performance.

57. Scenario: You need to maintain a private counter and expose only
limited methods to interact with it.

Question: How can closures be used to create private variables in JavaScript?

Answer:
Closures allow you to create private variables that are only accessible through specific
functions. A closure is formed when an inner function retains access to the outer function’s
variables, even after the outer function has executed. This pattern is useful for encapsulating
state.

For Example:

function createCounter() {
let count = 0;
return {
increment: () => count++,
getCount: () => count,
};
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
391

const counter = createCounter();


counter.increment();
console.log(counter.getCount()); // Output: 1

Here, count is private and only accessible through the provided methods.

58. Scenario: You want to reuse multiple behaviors across different classes
without using inheritance.

Question: How can mixins be used to share behaviors between classes?

Answer:
Mixins allow you to copy behaviors from one object or class to another, enabling code reuse
without inheritance. This technique provides flexibility to extend multiple classes with shared
functionality.

For Example:

const sayHelloMixin = {
sayHello() {
console.log(`Hello, ${this.name}`);
},
};

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

Object.assign(Person.prototype, sayHelloMixin);

const person = new Person('Alice');


person.sayHello(); // Output: Hello, Alice

Here, the sayHello behavior is added to the Person class dynamically.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
392

59. Scenario: You need to encapsulate multiple services into a single class
with a clear public interface.

Question: How can the Factory pattern be used to simplify object creation?

Answer:
The Factory pattern simplifies object creation by encapsulating the logic within a factory
function. This pattern decouples the client code from the specific class implementations,
making the code more maintainable and scalable.

For Example:

class Car {
constructor() {
this.type = 'Car';
}
}

class Bike {
constructor() {
this.type = 'Bike';
}
}

function VehicleFactory(vehicleType) {
if (vehicleType === 'car') return new Car();
if (vehicleType === 'bike') return new Bike();
}

const myCar = VehicleFactory('car');


console.log(myCar.type); // Output: Car

Here, the factory function abstracts object creation logic.

60. Scenario: You need to manage state and reactivity in a UI component.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
393

Question: How can the Module pattern be used to manage component state?

Answer:
The Module pattern helps encapsulate state and behavior within a component, exposing
only the necessary methods through a public API. This ensures controlled access to the
state, promoting a clean, maintainable structure.

For Example:

const CounterComponent = (function () {


let count = 0;
return {
increment: () => ++count,
getCount: () => count,
};
})();

CounterComponent.increment();
console.log(CounterComponent.getCount()); // Output: 1

Here, the counter state is encapsulated, ensuring that it can only be modified using the
provided methods.

61. Scenario: You need to create a function that performs an expensive API
request but ensures it is only executed once, no matter how many times
the function is called.

Question: How can you use the Singleton pattern to handle this scenario?

Answer:
The Singleton pattern ensures that a class or function is instantiated only once. This is useful
when making expensive or resource-heavy API calls, ensuring the function result is reused
without redundant executions. You can use closures to store the result of the first API call
and return the cached value on subsequent calls.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
394

const ApiService = (function () {


let instance;

async function createInstance() {


const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
return response.json();
}

return {
getInstance: async function () {
if (!instance) {
instance = await createInstance();
}
return instance;
},
};
})();

ApiService.getInstance().then(console.log); // Fetches and logs API data


ApiService.getInstance().then(console.log); // Logs cached data, no second fetch

In this example, the API request is made only once, and subsequent calls return the cached
result.

62. Scenario: You need to implement a rate-limiting mechanism to control


the frequency of a function that sends analytics data.

Question: How can you use throttling to achieve this?

Answer:
Throttling limits the frequency at which a function executes by ensuring that it runs at most
once every specified interval. This is helpful in scenarios like analytics, where continuous data
sending could overload the server.

For Example:

function throttle(func, limit) {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
395

let inThrottle;
return function (...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}

const sendAnalytics = throttle(() => {


console.log('Analytics sent!');
}, 5000);

setInterval(sendAnalytics, 1000); // Sends analytics every 5 seconds, despite


frequent triggers

In this example, analytics data is sent only once every 5 seconds, regardless of how often it is
triggered.

63. Scenario: You need to maintain the immutability of an object while


performing updates to nested properties.

Question: How can you use functional programming techniques to achieve this?

Answer:
In functional programming, immutability means that objects are not modified directly.
Instead, new objects are created with the necessary updates. Using the spread operator or
deep cloning techniques ensures that nested objects remain immutable.

For Example:

const user = {
name: 'Alice',
address: {
city: 'Wonderland',
postalCode: '12345'
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
396

};

const updatedUser = {
...user,
address: { ...user.address, city: 'Dreamland' }
};

console.log(user.address.city); // Output: Wonderland (original remains unchanged)


console.log(updatedUser.address.city); // Output: Dreamland

Here, the original object remains unchanged, and the new object reflects the updated city
value.

64. Scenario: You need to repeatedly fetch data but avoid unnecessary
duplicate requests.

Question: How can you use memoization to cache API responses?

Answer:
Memoization can be applied to API requests to cache responses and avoid redundant
network calls for the same input. If the same endpoint is requested again, the cached result
is returned.

For Example:

const memoizeFetch = (url) => {


const cache = {};
return async () => {
if (cache[url]) return cache[url];
const response = await fetch(url);
const data = await response.json();
cache[url] = data;
return data;
};
};

const getTodo = memoizeFetch('https://jsonplaceholder.typicode.com/todos/1');


getTodo().then(console.log); // Fetches and logs data

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
397

getTodo().then(console.log); // Logs cached data

Here, the API response is cached, and subsequent calls return the cached data.

65. Scenario: You need to execute multiple asynchronous tasks


sequentially, ensuring that each task completes before the next starts.

Question: How can you use async and await to achieve this?

Answer:
The async and await keywords allow you to write asynchronous code in a sequential
manner, improving readability. Each task waits for the previous one to complete before
starting the next.

For Example:

async function fetchTodosSequentially() {


const todo1 = await
fetch('https://jsonplaceholder.typicode.com/todos/1').then(res => res.json());
console.log(todo1);

const todo2 = await


fetch('https://jsonplaceholder.typicode.com/todos/2').then(res => res.json());
console.log(todo2);
}

fetchTodosSequentially();
// Logs todo1 and todo2 sequentially, ensuring the first completes before the
second starts

Here, the second request starts only after the first one completes.

66. Scenario: You need to create a reusable button component that tracks
how many times it has been clicked.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
398

Question: How can closures be used to encapsulate the click count?

Answer:
Closures allow encapsulating the click count variable inside the function, ensuring that it is
not accessible directly but is still maintained across invocations.

For Example:

function createButtonCounter() {
let count = 0;
return () => {
count++;
console.log(`Button clicked ${count} times`);
};
}

const buttonClickHandler = createButtonCounter();

document.getElementById('myButton').addEventListener('click', buttonClickHandler);

Each click increments the internal count variable, which is preserved across multiple clicks.

67. Scenario: You need to observe changes in an object and notify specific
listeners about the updates.

Question: How can the Observer pattern be implemented to handle this scenario?

Answer:
The Observer pattern allows objects to subscribe to a subject and receive notifications when
the subject’s state changes. This pattern is useful for tracking changes in state or events.

For Example:

class Observable {
constructor() {
this.observers = [];
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
399

subscribe(observer) {
this.observers.push(observer);
}

notify(data) {
this.observers.forEach((observer) => observer(data));
}
}

const observable = new Observable();

observable.subscribe((data) => console.log(`Observer 1: ${data}`));


observable.subscribe((data) => console.log(`Observer 2: ${data}`));

observable.notify('State changed');
// Output: Observer 1: State changed
// Observer 2: State changed

This example demonstrates how multiple observers react to state changes in the observable.

68. Scenario: You need to generate a sequence of numbers, but the next
value should be computed only when required.

Question: How can generators be used to lazily evaluate the sequence?

Answer:
Generators allow lazy evaluation, meaning the next value is generated only when requested.
This is useful for sequences or computations where calculating all values upfront is
unnecessary.

For Example:

function* numberGenerator() {
let i = 0;
while (true) {
yield i++;
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
400

const gen = numberGenerator();


console.log(gen.next().value); // Output: 0
console.log(gen.next().value); // Output: 1
console.log(gen.next().value); // Output: 2

Here, each next() call generates the next number in the sequence on demand.

69. Scenario: You want to manage dependencies between multiple UI


components dynamically.

Question: How can the Module pattern be used to encapsulate UI logic?

Answer:
The Module pattern helps encapsulate logic, exposing only necessary methods through a
public API. This ensures that internal state and behavior are hidden from other parts of the
application.

For Example:

const TabsModule = (function () {


let activeTab = 0;

return {
setActiveTab: (index) => {
activeTab = index;
console.log(`Active tab set to ${activeTab}`);
},
getActiveTab: () => activeTab,
};
})();

TabsModule.setActiveTab(2);
console.log(TabsModule.getActiveTab()); // Output: 2

This example encapsulates the active tab logic, providing controlled access to it.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
401

70. Scenario: You need to modify object behavior dynamically, such as


logging all property accesses.

Question: How can JavaScript Proxies be used to intercept and modify object
behavior?

Answer:
Proxies allow you to intercept operations on an object, such as property access or
assignment, enabling custom behavior (e.g., logging or validation).

For Example:

const target = { name: 'Alice' };


const handler = {
get: (obj, prop) => {
console.log(`Getting ${prop}`);
return obj[prop];
},
set: (obj, prop, value) => {
console.log(`Setting ${prop} to ${value}`);
obj[prop] = value;
return true;
},
};

const proxy = new Proxy(target, handler);

console.log(proxy.name); // Output: Getting name \n Alice


proxy.age = 25; // Output: Setting age to 25

In this example, the proxy logs all property accesses and modifications, giving full control
over object behavior.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
402

71. Scenario: You need to handle multiple dependent asynchronous


operations where the next operation depends on the result of the previous
one.

Question: How can async/await be used to manage dependent asynchronous


tasks?

Answer:
Using async and await, you can ensure that asynchronous tasks execute sequentially in a
readable, synchronous-like manner. Each await pauses the function execution until the
awaited promise resolves, ensuring the next operation only starts after the previous one
finishes. This is ideal when tasks depend on each other, such as fetching user data and using
it to retrieve related information (e.g., user posts).

For Example:

async function fetchUserData() {


const user = await fetch('https://jsonplaceholder.typicode.com/users/1').then(res
=> res.json());
console.log('User:', user);

const posts = await


fetch(`https://jsonplaceholder.typicode.com/posts?userId=${user.id}`).then(res =>
res.json());
console.log('User Posts:', posts);
}

fetchUserData();
// Output: First logs user data, then logs user posts

In this example, the second request waits for the user data before proceeding to fetch their
posts.

72. Scenario: You need to dynamically validate and control object property
access.

Question: How can JavaScript Proxies be used for validation?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
403

Answer:
Proxies allow intercepting operations on an object, such as getting or setting properties, by
using custom logic. For example, you can ensure that only valid data is assigned to a property
by adding validation logic in the set trap.

For Example:

const user = { name: 'Alice', age: 25 };


const handler = {
set: (obj, prop, value) => {
if (prop === 'age' && typeof value !== 'number') {
throw new Error('Age must be a number');
}
obj[prop] = value;
return true;
},
};

const proxy = new Proxy(user, handler);


proxy.age = 30; // Valid
console.log(proxy.age); // Output: 30

try {
proxy.age = 'thirty'; // Throws an error
} catch (e) {
console.error(e.message); // Output: Age must be a number
}

Here, the proxy enforces that the age property can only accept numbers, ensuring data
consistency.

73. Scenario: You want to ensure a complex function only executes if the
conditions are met, without nesting multiple if statements.

Question: How can you use function composition to simplify conditional


execution?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
404

Answer:
Function composition allows chaining multiple functions in a way that avoids deep nesting.
Each function performs a specific task, and the final composed function checks conditions
and acts accordingly.

For Example:

const isEven = (num) => num % 2 === 0;


const isPositive = (num) => num > 0;
const logResult = (result) => console.log(`Result: ${result}`);

const validateAndLog = (num) => {


if (isEven(num) && isPositive(num)) {
logResult(num);
}
};

validateAndLog(4); // Output: Result: 4


validateAndLog(-2); // No output (fails condition)

The composition ensures that multiple conditions are checked sequentially without nested
logic.

74. Scenario: You need to optimize multiple event listeners that fire
frequently, such as scroll events.

Question: How can you use both throttling and debouncing to manage event
listeners efficiently?

Answer:
Throttling ensures a function runs at most once every specified interval, while debouncing
delays execution until the event stops firing. Using both techniques, you can efficiently
manage event listeners.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
405

function throttle(func, limit) {


let inThrottle;
return function (...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}

function debounce(func, delay) {


let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), delay);
};
}

const handleScroll = throttle(() => console.log('Throttled Scroll!'), 1000);


const handleResize = debounce(() => console.log('Debounced Resize!'), 500);

window.addEventListener('scroll', handleScroll);
window.addEventListener('resize', handleResize);

Here, throttling and debouncing optimize event handling, reducing unnecessary calls.

75. Scenario: You need a factory function that creates objects with behavior
based on a configuration.

Question: How can the Factory pattern be used to achieve this?

Answer:
The Factory pattern abstracts object creation, allowing objects to be generated based on
input configurations. This simplifies complex object creation and promotes reusable code.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
406

function AnimalFactory(type) {
const animals = {
dog: () => ({ type: 'dog', sound: () => console.log('Bark!') }),
cat: () => ({ type: 'cat', sound: () => console.log('Meow!') }),
};
return animals[type] ? animals[type]() : null;
}

const dog = AnimalFactory('dog');


dog.sound(); // Output: Bark!

const cat = AnimalFactory('cat');


cat.sound(); // Output: Meow!

Here, the factory function dynamically creates animal objects based on the provided type.

76. Scenario: You need to efficiently accumulate multiple values into a


single result.

Question: How can the reduce method be used for accumulation?

Answer:
The reduce method applies a reducer function to each element of the array, accumulating
the result in a single value. This is useful for operations like summing, averaging, or merging
objects.

For Example:

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


const sum = numbers.reduce((acc, curr) => acc + curr, 0);
console.log(sum); // Output: 15

Here, reduce calculates the sum of all elements in the array.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
407

77. Scenario: You need to merge two objects without mutating the
originals.

Question: How can immutability be maintained when merging objects?

Answer:
Using the spread operator, you can merge objects into a new one without modifying the
original objects, ensuring immutability.

For Example:

const obj1 = { name: 'Alice' };


const obj2 = { age: 25 };

const mergedObj = { ...obj1, ...obj2 };


console.log(mergedObj); // Output: { name: 'Alice', age: 25 }

This approach preserves the original objects and returns a new merged object.

78. Scenario: You need to monitor changes to an object’s properties in real-


time.

Question: How can Proxies be used to watch object changes?

Answer:
Proxies allow you to intercept property changes, making it possible to log or react to
modifications in real time.

For Example:

const target = { name: 'Alice', age: 25 };


const handler = {
set: (obj, prop, value) => {
console.log(`Property ${prop} changed to ${value}`);
obj[prop] = value;
return true;

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
408

},
};

const proxy = new Proxy(target, handler);


proxy.age = 30; // Output: Property age changed to 30

Here, the proxy logs changes to the age property.

79. Scenario: You need to handle the result of multiple asynchronous


operations but continue even if some operations fail.

Question: How can Promise.allSettled be used for this scenario?

Answer:
Promise.allSettled waits for all promises to settle, returning an array of results, regardless
of whether the promises resolve or reject.

For Example:

const promise1 = Promise.resolve('Success 1');


const promise2 = Promise.reject('Error 2');

Promise.allSettled([promise1, promise2]).then((results) =>


console.log(results)
);
// Output: [{ status: 'fulfilled', value: 'Success 1' }, { status: 'rejected',
reason: 'Error 2' }]

This ensures all results are handled, even if some operations fail.

80. Scenario: You need to generate unique IDs for multiple objects to avoid
conflicts.

Question: How can Symbols be used to generate unique object keys?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
409

Answer:
Symbols generate unique values, ensuring that property keys do not collide, even with the
same description.

For Example:

const id1 = Symbol('id');


const id2 = Symbol('id');

const obj = {
[id1]: 'ID 1',
[id2]: 'ID 2',
};

console.log(obj[id1]); // Output: ID 1
console.log(obj[id2]); // Output: ID 2

Here, the symbols ensure that each ID key is unique, even though they share the same
description.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
410

Chapter 8 : JavaScript Frameworks and Libraries

THEORETICAL QUESTIONS
1. What is a component in React.js?

Answer:
In React, a component is a building block of the user interface (UI). It helps split the UI into
smaller, reusable pieces, each of which can manage its own logic and appearance.
Components can be functional (simpler and without lifecycle methods) or class-based (which
can use lifecycle methods). Functional components are preferred today because they are
simpler and, with hooks, allow managing state and side effects.

Components also accept props (short for "properties") to receive data from their parent
components, making them dynamic and reusable.

For Example:

function Welcome(props) {
return <h1>Hello, {props.name}!</h1>;
}

// Usage:
<Welcome name="Shardul" />;

Here, Welcome is a functional component that takes name as a prop and displays it. This
component can be reused with different names passed as props.

2. What is JSX in React.js?

Answer:
JSX stands for JavaScript XML. It allows writing HTML-like syntax directly inside JavaScript,
which makes it easy to design UI components. JSX also allows embedding JavaScript
expressions inside {} within the HTML-like structure. Under the hood, JSX is transformed by a
compiler like Babel into plain JavaScript calls using React.createElement().

JSX improves code readability by co-locating the markup with the logic.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
411

For Example:

const element = <h1>Hello, World!</h1>;

This JSX code translates to:

const element = React.createElement('h1', null, 'Hello, World!');

3. What are State and Props in React.js?

Answer:
In React, state and props are two ways of managing data in components:

1. State: This is internal to the component and can be changed by the component itself
using hooks like useState. When state changes, the component re-renders
automatically.
2. Props: These are read-only properties passed from a parent component to a child
component. They allow data to flow down the component tree but cannot be
modified by the child component.

For Example:

function Counter() {
const [count, setCount] = React.useState(0);

return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}

Here, count is a state managed by the Counter component. Clicking the button updates the
state, causing the component to re-render with the new value.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
412

4. What are React Hooks? Explain useState and useEffect.

Answer:
React Hooks are functions that let you use state and lifecycle methods inside functional
components. Before hooks, these features were only available in class components. Two of
the most common hooks are:

1. useState: Manages the component’s state.


2. useEffect: Handles side effects, such as fetching data, modifying the DOM, or setting
up subscriptions.

For Example:

function App() {
const [count, setCount] = React.useState(0);

React.useEffect(() => {
document.title = `Count: ${count}`;
}, [count]); // Dependency array to control re-runs

return (
<button onClick={() => setCount(count + 1)}>Click Me</button>
);
}

In this example, useEffect updates the document title whenever the count changes.

5. What is the React Router used for?

Answer:
React Router is a routing library used in React applications to manage navigation between
different views or components. It provides features like:

● Defining routes
● Navigating between pages without full-page reloads (SPA behavior)
● Handling dynamic URLs and route parameters

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
413

It uses the history API to push and replace routes without page refresh.

For Example:

import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';


import Home from './Home';
import About from './About';

function App() {
return (
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Router>
);
}

This code demonstrates routing between two components, Home and About.

6. What is the Context API in React?

Answer:
The Context API in React provides a way to share data across multiple components without
passing props down every level of the component tree. This prevents prop drilling, which
happens when props are passed through many layers unnecessarily. The createContext
function creates a context, and the Provider component supplies the context value.

For Example:

const UserContext = React.createContext();

function App() {
return (
<UserContext.Provider value={{ name: 'Shardul' }}>
<User />

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
414

</UserContext.Provider>
);
}

function User() {
const user = React.useContext(UserContext);
return <h1>Hello, {user.name}!</h1>;
}

Here, the User component consumes the UserContext without receiving props directly.

7. What is Vue.js used for?

Answer:
Vue.js is a progressive framework for building user interfaces. It focuses on the ViewModel
layer and is easy to integrate with other libraries. Vue is known for:

● Reactivity: Data changes are automatically reflected in the UI.


● Component-based structure: Similar to React, it allows creating reusable
components.
● Ease of adoption: It’s easier to start with compared to some other frameworks like
Angular.

8. What is data binding in Vue.js?

Answer:
Data binding in Vue.js connects the UI elements to the data model. Vue supports:

● One-way data binding: Binding data from the component to the view using v-bind.
● Two-way data binding: Binding data between the view and the component using v-
model.

For Example:

<div id="app">

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
415

<input v-model="message" />


<p>{{ message }}</p>
</div>

<script>
new Vue({
el: '#app',
data: {
message: 'Hello, Vue!'
}
});
</script>

The v-model directive ensures that changes in the input are reflected in the message, and
vice versa.

9. What is Vuex in Vue.js?

Answer:
Vuex is a state management library for Vue.js. It provides a centralized store where the state
of the entire application is kept, ensuring consistent state across components. It allows:

● Mutations: Changing state.


● Actions: Performing asynchronous operations.
● Getters: Retrieving computed data.

10. What is Node.js?

Answer:
Node.js is a JavaScript runtime built on Chrome’s V8 engine. It allows running JavaScript
outside the browser, making it ideal for server-side programming. Node.js is asynchronous
and event-driven, meaning it can handle multiple requests without blocking the main
thread. This makes it suitable for real-time applications.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
416

const http = require('http');

const server = http.createServer((req, res) => {


res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello, World!\n');
});

server.listen(3000, () => {
console.log('Server running at http://localhost:3000/');
});

This code creates a simple HTTP server that responds with "Hello, World!" when accessed.

11. What is Express.js in Node.js?

Answer:
Express.js is a minimal and flexible web framework built on top of Node.js that helps in
building web applications and APIs easily. It abstracts much of the underlying complexities of
creating a Node.js server, providing a simpler way to handle routing, middleware, and HTTP
requests. Express is widely used for building RESTful APIs, serving web pages, or managing
real-time applications using frameworks like Socket.IO. Its modular nature allows developers
to add only the necessary components, keeping the application lightweight.

For Example:

const express = require('express');


const app = express();

app.get('/', (req, res) => {


res.send('Hello, World!');
});

app.listen(3000, () => {
console.log('Server is running on http://localhost:3000');
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
417

Here, an Express server is created that listens on port 3000. When someone accesses the root
URL (/), it responds with “Hello, World!”.

12. What is middleware in Express.js?

Answer:
Middleware functions in Express.js are functions that execute during the lifecycle of a
request to the server. These functions have access to the request (req), response (res), and a
function called next, which passes control to the next middleware in the sequence.
Middleware is commonly used for logging, authentication, handling errors, and modifying
request/response objects.

For Example:

const express = require('express');


const app = express();

// Logging middleware
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
});

app.get('/', (req, res) => {


res.send('Hello, Middleware!');
});

app.listen(3000, () => console.log('Server running on http://localhost:3000'));

In this example, the middleware logs every request before the response is sent. The call to
next() ensures the request moves to the next middleware or route handler.

13. What is file handling in Node.js?

Answer:
File handling in Node.js refers to working with the file system to perform operations like

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
418

reading, writing, updating, deleting, and renaming files. Node.js provides the fs (File System)
module, which supports both synchronous and asynchronous file operations. Asynchronous
operations are preferred since they do not block the event loop.

For Example:

const fs = require('fs');

// Write content to a new file


fs.writeFile('example.txt', 'Hello, Node.js!', (err) => {
if (err) throw err;
console.log('File created and data written!');
});

This example creates a new file named example.txt and writes data to it. If an error occurs, it
is handled with the callback function.

14. What is two-way data binding in Angular?

Answer:
Two-way data binding in Angular synchronizes data between the model (component) and
the view (template). When the user updates the value in the view, it reflects in the model
automatically, and vice versa. This is achieved using the ngModel directive, which allows
Angular to connect form elements with the component’s data properties.

For Example:

<input [(ngModel)]="name" placeholder="Enter your name" />


<p>Hello, {{ name }}!</p>

In this example, whatever the user types into the input field will be reflected in the name
variable in the component, and the paragraph will display the updated value in real-time.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
419

15. What is a service in Angular?

Answer:
A service in Angular is a class that contains reusable business logic or data-sharing logic,
which can be injected into components or other services. Services make the code modular,
maintainable, and testable by separating logic from components. Angular uses dependency
injection (DI) to provide instances of services where needed.

For Example:

import { Injectable } from '@angular/core';

@Injectable({
providedIn: 'root', // Automatically provided throughout the app
})
export class DataService {
getData() {
return ['Angular', 'React', 'Vue'];
}
}

Here, the DataService is marked as @Injectable, meaning it can be injected into other
components using Angular’s dependency injection.

16. What is dependency injection (DI) in Angular?

Answer:
Dependency Injection (DI) is a design pattern in Angular that provides a way to supply
components with their dependencies, such as services. Angular’s DI system ensures that
services or dependencies are instantiated only once and reused where needed. This
promotes modular, maintainable, and testable code by keeping component logic separate
from dependencies.

17. What is Vue CLI, and why is it used?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
420

Answer:
The Vue CLI (Command Line Interface) is a tool that helps developers scaffold and configure
Vue.js projects quickly. It provides a project structure with all necessary tools (like Webpack
and Babel) configured out of the box. Vue CLI also supports hot module replacement (HMR),
which updates parts of the page without a full reload, speeding up development.

For Example:

vue create my-vue-app

This command initializes a new Vue project with a pre-configured structure and tools for
development and production builds.

18. What is the event loop in Node.js?

Answer:
The event loop in Node.js is the mechanism that allows it to handle non-blocking,
asynchronous operations. Even though JavaScript is single-threaded, the event loop enables
Node.js to perform I/O operations in the background, such as reading files or making
network requests, without blocking the main thread. The event loop continuously checks for
pending tasks and executes their callbacks when ready.

19. What is the difference between npm and npx?

Answer:

● npm (Node Package Manager): Used to install and manage packages or


dependencies for Node.js projects. Dependencies are added to the node_modules
folder, and their metadata is stored in the package.json file.
● npx: A utility that comes with npm. It allows running Node binaries without globally
installing them. This is useful for temporary commands or when using tools like
create-react-app.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
421

npx create-react-app my-app

This command creates a new React application without requiring the developer to install
create-react-app globally.

20. What are Angular directives?

Answer:
Directives in Angular are instructions that tell Angular to do something with a DOM
element. There are three types of directives:

1. Structural directives: These alter the structure of the DOM, adding or removing
elements (e.g., *ngIf, *ngFor).
2. Attribute directives: These change the behavior or appearance of an element (e.g.,
ngClass, ngStyle).
3. Component directives: Custom components are also treated as directives because
they attach behavior to a template.

For Example:

<p *ngIf="isVisible">This text is conditionally visible.</p>

The *ngIf directive adds or removes the paragraph based on the value of isVisible. If
isVisible is true, the paragraph is displayed; otherwise, it is removed from the DOM.

21. What is the Virtual DOM in React, and how does it improve
performance?

Answer:
The Virtual DOM is an abstraction of the real DOM. It allows React to manage UI updates

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
422

efficiently by keeping an in-memory representation of the actual DOM. When a component’s


state or props change, React creates a new virtual DOM tree and compares it with the
previous one using reconciliation. This comparison identifies only the parts that changed.
Instead of re-rendering the entire DOM, React updates only those specific elements,
minimizing costly DOM operations.

This process, known as diffing, ensures smooth and fast updates, enhancing the
performance of the app, especially for large, dynamic interfaces.

22. Explain Higher-Order Components (HOC) in React.

Answer:
A Higher-Order Component (HOC) is a pattern for code reuse in React. It takes a
component as input and returns a new component with additional logic. This pattern allows
you to reuse functionality across multiple components without duplicating code. HOCs are
often used to add cross-cutting concerns such as authentication, logging, or fetching data.

HOCs do not modify the original component but instead wrap it in another component with
extra logic.

For Example:

const withLogger = (Component) => {


return function EnhancedComponent(props) {
console.log(`Rendering ${Component.name}`);
return <Component {...props} />;
};
};

const Hello = () => <h1>Hello, World!</h1>;


const LoggedHello = withLogger(Hello);

Here, LoggedHello is an enhanced version of Hello that logs messages whenever it renders.

23. What are React Portals, and when should you use them?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
423

Answer:
React Portals allow rendering a component’s content outside its natural parent component
hierarchy in the DOM. This is helpful when dealing with modals, tooltips, and pop-ups, as it
allows better control over positioning and z-index.

For Example:

ReactDOM.createPortal(
<div className="modal">I am rendered via a Portal!</div>,
document.getElementById('portal-root')
);

The portal ensures that the modal content is rendered at the portal-root div, avoiding any
layout issues from being nested inside deeply nested parent components.

24. What is lazy loading in React, and how do you implement it?

Answer:
Lazy loading in React delays the loading of a component until it is required. This is
particularly useful in large applications where loading all components upfront can slow
down the initial page load. With lazy loading, components are loaded on-demand, improving
performance by reducing the bundle size.

For Example:

const LazyComponent = React.lazy(() => import('./MyComponent'));

function App() {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</React.Suspense>
);
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
424

In this example, MyComponent is not loaded until it’s needed. While it loads, the fallback UI
(Loading...) is shown.

25. What are Vue.js lifecycle hooks, and why are they important?

Answer:
Lifecycle hooks in Vue.js allow developers to execute code at different stages of a
component’s life. These hooks include:

● created(): Called after the component is created.


● mounted(): Called after the component is inserted into the DOM.
● updated(): Called when data changes trigger a re-render.
● destroyed(): Called before the component is removed from the DOM.

These hooks are useful for fetching data, setting up subscriptions, and cleaning up
resources.

For Example:

export default {
mounted() {
console.log('Component has been mounted.');
},
};

26. Explain the concept of watch in Vue.js.

Answer:
The watch property in Vue.js is used to monitor changes in reactive data and execute code in
response. Unlike computed properties, which cache values, watch functions are used when
you need to perform side effects like making API calls or updating the DOM.

For Example:

export default {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
425

data() {
return { count: 0 };
},
watch: {
count(newValue) {
console.log('Count changed to:', newValue);
},
},
};

Here, whenever count changes, the watch function logs the new value.

27. What is middleware chaining in Express.js?

Answer:
In Express.js, middleware functions are executed in sequence using chaining. Each
middleware function must call the next() function to pass control to the next middleware.
This pattern allows for complex request flows where tasks like logging, authentication, and
validation happen in stages.

For Example:

app.use((req, res, next) => {


console.log('First Middleware');
next();
});

app.use((req, res, next) => {


console.log('Second Middleware');
next();
});

Here, both middleware functions execute sequentially before the final response is sent.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
426

28. What is the difference between process.nextTick() and


setImmediate() in Node.js?

Answer:

● process.nextTick() schedules the callback to run immediately after the current


operation completes, but before the event loop continues.
● setImmediate() schedules the callback to execute at the start of the next iteration
of the event loop.

For Example:

console.log('Start');

process.nextTick(() => console.log('Next Tick'));


setImmediate(() => console.log('Set Immediate'));

console.log('End');

Output:

Start
End
Next Tick
Set Immediate

This demonstrates that process.nextTick() executes before setImmediate().

29. How does Angular handle forms (Template-driven vs Reactive)?

Answer:
Angular provides two ways to manage forms:

1. Template-driven forms: Defined in the template using Angular directives (ngModel).


They are simple and suitable for smaller forms but offer less control.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
427

2. Reactive forms: Managed programmatically in the component using FormGroup and


FormControl. These forms are more powerful and dynamic, suitable for complex use
cases.

For Example (Reactive Form):

import { FormGroup, FormControl } from '@angular/forms';

this.form = new FormGroup({


name: new FormControl(''),
age: new FormControl(''),
});

This example creates a form with two fields: name and age.

30. What is ngx-bootstrap, and how is it used in Angular?

Answer:
ngx-bootstrap is an Angular library that provides ready-to-use Bootstrap components. It
allows developers to use Bootstrap elements like modals, dropdowns, and date pickers in
Angular applications, with seamless integration of Angular’s templating and reactive forms.

For Example:

npm install ngx-bootstrap --save

After installing, import the required Bootstrap modules:

import { BsDropdownModule } from 'ngx-bootstrap/dropdown';

@NgModule({
imports: [BsDropdownModule.forRoot()],
})
export class AppModule {}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
428

This code adds Bootstrap’s dropdown functionality to the Angular project.

31. What is Server-Side Rendering (SSR) in React, and how is it different


from Client-Side Rendering (CSR)?

Answer:
Server-Side Rendering (SSR) in React involves rendering the initial HTML of a component on
the server and sending it to the client. This results in a faster first contentful paint (FCP),
enhancing SEO because search engines can easily index the pre-rendered HTML. On the
other hand, Client-Side Rendering (CSR) sends a blank HTML shell to the client and renders
components dynamically through JavaScript. While CSR offers smoother transitions for
interactive elements, it may result in slower initial load times.

SSR is particularly beneficial for content-heavy websites, such as blogs or e-commerce


platforms, where SEO matters. Next.js is a popular framework that facilitates SSR for React.

32. What is the difference between Redux and Context API in React?

Answer:

● Redux: A state management library that maintains the entire application state in a
single store. State transitions in Redux are predictable because they are driven by
actions and reducers. Redux is ideal for managing global states that are complex and
shared across many components (e.g., authentication state, user profiles).
● Context API: Built into React, it is a simpler way to share state across components
without passing props manually. While Context API works well for small applications
or lightweight global state like theming, it becomes cumbersome for large
applications with deep component hierarchies.

33. What are Vue.js Mixins, and how are they used?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
429

Answer:
In Vue.js, mixins provide a way to share reusable logic between components. Components
that use the same logic (like handling API responses or timers) can inherit this functionality
from a mixin. Mixins can contain data, methods, computed properties, and lifecycle hooks.
When conflicts arise between component methods and mixin methods, the component’s
implementation takes precedence.

For Example:

const dataMixin = {
data() {
return {
message: 'Mixin message',
};
},
methods: {
showMessage() {
console.log(this.message);
},
},
};

export default {
mixins: [dataMixin],
mounted() {
this.showMessage(); // Logs: Mixin message
},
};

This component inherits data and methods from dataMixin and uses them during its
lifecycle.

34. Explain the Observer Pattern used in Node.js streams.

Answer:
The Observer Pattern in Node.js is implemented through EventEmitters and streams. In this
pattern, objects (observers) subscribe to events emitted by a subject. When an event occurs,
all subscribed observers are notified. Streams follow this pattern by emitting data events in
chunks, notifying listeners about each chunk received.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
430

This design allows Node.js to handle asynchronous I/O efficiently, as streams can process
data incrementally without waiting for the complete data to arrive.

35. What is Dependency Injection in Angular, and how does it differ from
Service Locator?

Answer:
In Dependency Injection (DI), components receive their dependencies externally through
injection. Angular’s DI framework allows you to inject services into components or other
services, promoting loose coupling and better testability. In contrast, the Service Locator
Pattern requires components to request their dependencies from a centralized registry,
which leads to tighter coupling between components and dependencies.

36. How does Node.js handle child processes?

Answer:
Node.js uses the child_process module to create child processes that run shell commands
or separate scripts. These processes allow the main event loop to remain non-blocking,
enabling tasks like heavy computations or file handling to run in parallel.

Node.js supports different ways of creating child processes:

1. exec(): Runs a command and returns the output.


2. spawn(): Creates a stream-based child process.
3. fork(): Used for spawning new Node.js processes.

For Example:

const { exec } = require('child_process');

exec('ls', (error, stdout, stderr) => {


if (error) {
console.error(`Error: ${error.message}`);
}
console.log(`Output: ${stdout}`);
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
431

This example runs the ls command, listing the files in the current directory.

37. What are Angular Guards, and how do they work?

Answer:
Angular Guards are functions that control access to routes in an application. These guards
ensure that users can only access specific routes if certain conditions are met (e.g., being
authenticated). There are several types of route guards:

● CanActivate: Determines if a user can navigate to a route.


● CanDeactivate: Checks if navigation away from a route is allowed.
● CanLoad: Controls if a lazy-loaded module should be loaded.

For Example:

import { Injectable } from '@angular/core';


import { CanActivate } from '@angular/router';

@Injectable({
providedIn: 'root',
})
export class AuthGuard implements CanActivate {
canActivate(): boolean {
return confirm('Are you logged in?');
}
}

This example asks the user for confirmation before navigating to a protected route.

38. What is IndexedDB, and how does it differ from LocalStorage?

Answer:
IndexedDB is a low-level, asynchronous storage API that allows storing structured data in
the browser. It is useful for large datasets, such as user data or offline applications, since it
supports transactions and queries. Unlike LocalStorage, which only stores key-value pairs
synchronously, IndexedDB supports object stores for complex data structures and provides
greater storage capacity.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
432

39. How does WebSocket work in Node.js, and what are its advantages?

Answer:
WebSocket is a protocol that provides full-duplex communication over a single TCP
connection. Unlike HTTP, which is request-response-based, WebSocket allows both the
client and server to send messages at any time. This makes it ideal for real-time
applications, such as chat apps or stock trading platforms.

For Example:

const WebSocket = require('ws');


const server = new WebSocket.Server({ port: 8080 });

server.on('connection', (ws) => {


ws.send('Welcome!');
ws.on('message', (message) => console.log('Received:', message));
});

This example sets up a WebSocket server that sends a welcome message to new
connections.

40. What is Progressive Web App (PWA), and how is it implemented in


Angular?

Answer:
A Progressive Web App (PWA) is a web application that provides offline capabilities, push
notifications, and installability like a native mobile app. PWAs leverage service workers to
cache resources, allowing them to function offline. Angular makes it easy to add PWA
capabilities using the @angular/pwa package.

For Example:

ng add @angular/pwa

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
433

This command configures service workers and updates the Angular project to behave as a
PWA. Once configured, the app can cache static assets and serve them offline, improving
performance and reliability.

SCENARIO QUESTIONS
41. Scenario: A developer is building a product listing page in React and
needs to display each product in a reusable way.

Question: How can React components and props help in creating reusable product cards for
the product listing page?

Answer:
In React, components allow developers to create reusable elements that encapsulate both
logic and UI. To customize the output of each component, props (short for properties) are
passed to them. This way, the product listing page can use the same ProductCard
component for multiple products by passing product-specific data via props.

Using components and props promotes reusability and clean code by avoiding duplicate
structures across the application.

For Example:

function ProductCard({ name, price }) {


return (
<div className="product-card">
<h2>{name}</h2>
<p>Price: ${price}</p>
</div>
);
}

export default function ProductList() {


const products = [
{ name: 'Product 1', price: 50 },
{ name: 'Product 2', price: 30 },
];

return (
<div>

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
434

{products.map((product, index) => (


<ProductCard key={index} name={product.name} price={product.price} />
))}
</div>
);
}

In this example, the ProductCard component is reused for multiple products by passing the
name and price props.

42. Scenario: You need to toggle between a light and dark theme in a
React application.

Question: How can useState and useEffect hooks be used to toggle between light and
dark themes in React?

Answer:
In React, the useState hook manages the theme’s current state (light or dark). The
useEffect hook is used to apply the selected theme to the document body whenever the
state changes. This setup allows users to switch between themes and ensures the theme
persists throughout the session.

For Example:

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

export default function ThemeToggler() {


const [theme, setTheme] = useState('light');

useEffect(() => {
document.body.className = theme;
}, [theme]);

return (
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Switch to {theme === 'light' ? 'Dark' : 'Light'} Theme
</button>
);
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
435

Here, useState tracks the current theme, and useEffect updates the document's body class
whenever the theme changes.

43. Scenario: A React application needs multiple pages, such as Home and
About, with navigation between them.

Question: How can React Router be used to implement routing between the Home and
About pages?

Answer:
React Router provides a way to manage client-side navigation in single-page applications
(SPA). It allows the creation of multiple pages, each represented by a route, without
reloading the entire page. Using Routes and Link, developers can define routes and provide
seamless navigation between components.

For Example:

import { BrowserRouter as Router, Route, Routes, Link } from 'react-router-dom';

function Home() {
return <h2>Home Page</h2>;
}

function About() {
return <h2>About Page</h2>;
}

export default function App() {


return (
<Router>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
436

</Router>
);
}

In this example, Router wraps the entire app, and links allow navigation between the Home
and About pages.

44. Scenario: You need to manage a large state tree in a Vue.js application.

Question: How can Vuex be used to manage global state effectively across components?

Answer:
Vuex is a state management library for Vue.js applications that provides a centralized store
for managing global state. It allows multiple components to access and modify shared state
without relying on prop drilling. State changes in Vuex are handled through mutations and
actions to ensure predictable state transitions.

For Example:

// store.js
import { createStore } from 'vuex';

export const store = createStore({


state: {
counter: 0,
},
mutations: {
increment(state) {
state.counter++;
},
},
});

// main.js
import { createApp } from 'vue';
import { store } from './store';
import App from './App.vue';

const app = createApp(App);


app.use(store).mount('#app');

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
437

// App.vue
<template>
<div>
<p>Counter: {{ $store.state.counter }}</p>
<button @click="$store.commit('increment')">Increment</button>
</div>
</template>

Here, the Vuex store manages the state, and the component interacts with the state through
mutations.

45. Scenario: You are building an Express.js API that requires logging
request details.

Question: How can middleware be used in Express.js to log incoming HTTP requests?

Answer:
Middleware functions in Express.js are functions that execute during the request-response
lifecycle. Logging incoming requests helps in monitoring and debugging. Middleware
functions can be registered using app.use() to run on every request.

For Example:

const express = require('express');


const app = express();

// Logging middleware
app.use((req, res, next) => {
console.log(`${req.method} request to ${req.url}`);
next();
});

app.get('/', (req, res) => res.send('Hello, Express!'));

app.listen(3000, () => console.log('Server running on http://localhost:3000'));

In this example, the middleware logs each request before passing control to the next
handler.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
438

46. Scenario: You need to create a service in Angular to share data between
components.

Question: How can a shared service be implemented and injected into multiple Angular
components?

Answer:
Angular services are classes that contain reusable logic, and they can be shared across
components using dependency injection (DI). A service is created with the @Injectable
decorator and provided in the root module to make it globally accessible.

For Example:

// data.service.ts
import { Injectable } from '@angular/core';

@Injectable({
providedIn: 'root',
})
export class DataService {
getData() {
return ['Angular', 'React', 'Vue'];
}
}

// app.component.ts
import { Component } from '@angular/core';
import { DataService } from './data.service';

@Component({
selector: 'app-root',
template: `<ul><li *ngFor="let item of data">{{ item }}</li></ul>`,
})
export class AppComponent {
data: string[];
constructor(private dataService: DataService) {
this.data = this.dataService.getData();
}
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
439

The DataService is injected into the component and used to retrieve data.

47. Scenario: You need to handle file uploads in a Node.js API.

Question: How can file uploads be handled in Node.js using middleware?

Answer:
In Node.js, the multer middleware is commonly used to handle file uploads. It processes
incoming files and stores them on the server.

For Example:

const express = require('express');


const multer = require('multer');
const upload = multer({ dest: 'uploads/' });

const app = express();

app.post('/upload', upload.single('file'), (req, res) => {


res.send(`File uploaded: ${req.file.originalname}`);
});

app.listen(3000, () => console.log('Server running on http://localhost:3000'));

This example accepts file uploads and stores them in the uploads directory.

48. Scenario: You want to ensure form inputs are synchronized between
the template and component in Angular.

Question: How can two-way data binding be implemented in Angular forms?

Answer:
In Angular, two-way data binding allows synchronization between the component’s state
and the form inputs using the ngModel directive.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
440

<input [(ngModel)]="name" placeholder="Enter your name" />


<p>Hello, {{ name }}!</p>

Here, changes in the input field reflect in the name variable, and vice versa.

49. Scenario: You need to create a RESTful API endpoint in Express.js that
returns a list of users.

Question: How can a GET endpoint be implemented in Express.js to return data?

Answer:
Express.js simplifies the creation of RESTful endpoints. A GET endpoint can be defined using
the app.get() method to serve data.

For Example:

const express = require('express');


const app = express();

app.get('/users', (req, res) => {


const users = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
res.json(users);
});

app.listen(3000, () => console.log('Server running on http://localhost:3000'));

This example serves a list of users as a JSON response.

50. Scenario: You need to fetch data from an API and display it in a Vue
component.

Question: How can data be fetched asynchronously in Vue using lifecycle hooks?

Answer:
Vue’s mounted() lifecycle hook can be used to fetch data when the component is initialized.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
441

export default {
data() {
return { users: [] };
},
async mounted() {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
this.users = await response.json();
},
template: `<ul><li v-for="user in users">{{ user.name }}</li></ul>`,
};

This example fetches data from an API and displays it in the component.

51. Scenario: You need to build a simple form in React to collect user data.

Question: How can you manage form state using useState in React?

Answer:
Managing form inputs in React involves using the useState hook to store and update the
form data. Each input field can have its own state, or a single state object can track the entire
form. Using controlled components ensures that the form values are in sync with the
component’s state.

For Example:

import React, { useState } from 'react';

function UserForm() {
const [formData, setFormData] = useState({ name: '', email: '' });

const handleChange = (e) => {


const { name, value } = e.target;
setFormData((prev) => ({ ...prev, [name]: value }));
};

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
442

const handleSubmit = (e) => {


e.preventDefault();
console.log('Form Submitted:', formData);
};

return (
<form onSubmit={handleSubmit}>
<input
type="text"
name="name"
value={formData.name}
onChange={handleChange}
placeholder="Name"
/>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
placeholder="Email"
/>
<button type="submit">Submit</button>
</form>
);
}

export default UserForm;

This example collects name and email inputs, and logs the form data on submission.

52. Scenario: You need to pass data from a parent component to a child
component in React.

Question: How can props be used to pass data between components in React?

Answer:
In React, props (properties) allow the parent component to pass data to child components.
Props are immutable, meaning the child component cannot modify them directly. This
pattern promotes unidirectional data flow, where data flows from parent to child.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
443

For Example:

function Child({ message }) {


return <h1>{message}</h1>;
}

function Parent() {
return <Child message="Hello from Parent!" />;
}

Here, the Parent component passes the message prop to the Child component, which
displays it.

53. Scenario: You need to display a list of items dynamically in Vue.js.

Question: How can the v-for directive be used to render lists in Vue.js?

Answer:
In Vue.js, the v-for directive allows developers to render lists by looping through an array of
data. Each item in the array is rendered as a new element.

For Example:

<ul>
<li v-for="(item, index) in items" :key="index">{{ item }}</li>
</ul>

<script>
export default {
data() {
return {
items: ['Apple', 'Banana', 'Orange'],
};
},
};

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
444

</script>

This example renders a list of fruits dynamically.

54. Scenario: You need to handle errors gracefully in an Express.js API.

Question: How can Express.js handle errors using middleware?

Answer:
In Express.js, error-handling middleware catches and processes errors in a centralized way.
The middleware function must include four parameters: (err, req, res, next).

For Example:

const express = require('express');


const app = express();

app.get('/', (req, res) => {


throw new Error('Something went wrong!');
});

// Error-handling middleware
app.use((err, req, res, next) => {
console.error(err.message);
res.status(500).send('Internal Server Error');
});

app.listen(3000, () => console.log('Server running on http://localhost:3000'));

This example catches errors thrown by routes and returns a 500 status with a message.

55. Scenario: You need to bind an HTML element’s attribute to data in


Vue.js.

Question: How can the v-bind directive be used to bind attributes dynamically in Vue.js?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
445

Answer:
The v-bind directive binds HTML attributes to Vue data. It allows dynamic updating of
attributes based on the component’s state.

For Example:

<img v-bind:src="imageUrl" alt="Dynamic Image" />

<script>
export default {
data() {
return {
imageUrl: 'https://via.placeholder.com/150',
};
},
};
</script>

Here, the src attribute of the <img> tag is bound to imageUrl.

56. Scenario: You need to build a RESTful API that supports JSON parsing in
Express.js.

Question: How can Express.js handle JSON data in requests?

Answer:
Express.js provides the express.json() middleware to parse incoming JSON payloads. This
middleware ensures that req.body contains the parsed data from the request.

For Example:

const express = require('express');


const app = express();

app.use(express.json());

app.post('/data', (req, res) => {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
446

console.log(req.body);
res.send('Data received');
});

app.listen(3000, () => console.log('Server running on http://localhost:3000'));

This example accepts and logs JSON data sent to the /data endpoint.

57. Scenario: You need to create and register a new component in Vue.js.

Question: How can a component be created and used within another Vue.js component?

Answer:
In Vue.js, components can be registered locally or globally. A locally registered component is
available only within the parent component.

For Example:

// ChildComponent.vue
<template>
<p>This is a child component.</p>
</template>

// ParentComponent.vue
<template>
<ChildComponent />
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
components: {
ChildComponent,
},
};
</script>

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
447

The ParentComponent imports and registers the ChildComponent for use in its template.

58. Scenario: You want to control the loading of a Vue.js component based
on a condition.

Question: How can the v-if directive be used to conditionally render elements in Vue.js?

Answer:
The v-if directive allows conditional rendering of elements in Vue. If the condition evaluates
to true, the element is rendered; otherwise, it is not.

For Example:

<p v-if="isVisible">This text is visible.</p>

<script>
export default {
data() {
return { isVisible: true };
},
};
</script>

The paragraph is displayed only if isVisible is true.

59. Scenario: You need to create a service that performs API calls in
Angular.

Question: How can an Angular service make HTTP requests using HttpClient?

Answer:
In Angular, the HttpClient service is used to perform HTTP requests. It is injected into
services to encapsulate logic for API calls.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
448

For Example:

import { Injectable } from '@angular/core';


import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
providedIn: 'root',
})
export class DataService {
constructor(private http: HttpClient) {}

getData(): Observable<any> {
return this.http.get('https://jsonplaceholder.typicode.com/posts');
}
}

This service fetches posts from a public API using HttpClient.

60. Scenario: You need to manage asynchronous operations with promises


in Node.js.

Question: How can async/await be used in Node.js to handle promises?

Answer:
In Node.js, async/await simplifies working with promises, making asynchronous code easier
to read and write. It allows you to wait for a promise to resolve before continuing execution.

For Example:

const fetchData = async () => {


try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Error fetching data:', error);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
449

}
};

fetchData();

This example uses async/await to fetch and log data from an API.

61. Scenario: You need to optimize the performance of a large React


application that frequently re-renders components.

Question: How can React.memo help in optimizing component re-renders in React?

Answer:
React.memo is a higher-order component (HOC) that optimizes functional components by
preventing unnecessary re-renders. If a parent component re-renders, all its child
components are also re-rendered, even if their props have not changed. With React.memo,
the component only re-renders when its props or state change. This saves processing time
and improves performance.

How it works:
React.memo performs a shallow comparison between the previous and new props. If the
values are identical, the component is not re-rendered. For deep comparisons or complex
logic, a custom comparison function can be provided.

For Example:

const Child = React.memo(({ name }) => {


console.log('Child component rendered');
return <h1>Hello, {name}!</h1>;
});

function Parent() {
const [count, setCount] = React.useState(0);
return (
<div>

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
450

<button onClick={() => setCount(count + 1)}>Increment</button>


<Child name="Shardul" />
</div>
);
}

Explanation:
Even if the parent component re-renders every time the count changes, the Child
component will not re-render because its name prop remains the same. This reduces
unnecessary re-rendering.

62. Scenario: You want to manage complex state transitions in a React


component.

Question: How does the useReducer hook manage complex state logic in React?

Answer:
The useReducer hook is an alternative to useState for managing more complex state
transitions. It follows a similar concept to Redux, using a reducer function to control how the
state changes based on actions.

How it works:

● Reducer function: A function that takes the current state and an action, returning
the updated state.
● Dispatch function: Used to trigger state changes by passing action objects.

This is useful for components with multiple state variables that are tightly related or for when
the next state depends on the previous state.

For Example:

const initialState = { count: 0 };

function reducer(state, action) {


switch (action.type) {
case 'increment':
return { count: state.count + 1 };

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
451

case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}

function Counter() {
const [state, dispatch] = React.useReducer(reducer, initialState);

return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
<button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
</div>
);
}

Explanation:
The reducer function determines how the state changes based on the action type. dispatch
triggers the actions, allowing the state to update in a predictable way.

63. Scenario: You need to share state globally across multiple components
in React.

Question: How can the Context API be used to share global state in React?

Answer:
The Context API provides a way to share global state across multiple components without
having to pass props through multiple layers (prop drilling). It allows components to
consume state directly from the context without receiving it as props from parent
components.

How it works:

1. Create a context using React.createContext().


2. Provide state using the Provider component.
3. Consume state in child components using useContext().

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
452

For Example:

const UserContext = React.createContext();

function App() {
return (
<UserContext.Provider value={{ name: 'Shardul' }}>
<User />
</UserContext.Provider>
);
}

function User() {
const user = React.useContext(UserContext);
return <h1>Hello, {user.name}!</h1>;
}

Explanation:
The User component accesses the global state (name) directly from UserContext using
useContext(). This eliminates the need for prop drilling, making the code more
maintainable.

64. Scenario: You want to cache API responses in a Vue.js component to


reduce network requests.

Question: How can Vue’s computed properties be used to optimize API calls?

Answer:
Computed properties in Vue.js are reactive and cached based on their dependencies. If the
dependencies do not change, Vue returns the cached result instead of recomputing the
value. This makes computed properties ideal for reducing unnecessary calculations or API
requests.

For Example:

export default {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
453

data() {
return { query: '', results: [] };
},
computed: {
filteredResults() {
return this.results.filter((item) =>
item.toLowerCase().includes(this.query.toLowerCase())
);
},
},
async mounted() {
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
this.results = await response.json();
},
};

Explanation:
The filteredResults computed property recalculates only when the query changes. If the
query remains the same, Vue returns the cached value, improving performance.

65. Scenario: You need to handle cross-origin requests in an Express.js API.

Question: How can CORS (Cross-Origin Resource Sharing) be enabled in an Express.js


application?

Answer:
CORS (Cross-Origin Resource Sharing) allows a server to accept requests from a different
domain. By default, browsers block cross-origin requests for security reasons. In Express.js,
the cors middleware enables CORS by setting the appropriate headers.

For Example:

const express = require('express');


const cors = require('cors');
const app = express();

app.use(cors());

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
454

app.get('/', (req, res) => res.send('CORS enabled!'));

app.listen(3000, () => console.log('Server running on http://localhost:3000'));

Explanation:
The cors middleware enables cross-origin requests for all routes in the Express application.

66. Scenario: You need to secure sensitive data in an Angular service.

Question: How can Angular’s environment files be used to manage sensitive configurations?

Answer:
Angular provides environment files to manage configuration settings based on the build
environment (e.g., development, production). This ensures that sensitive data, like API keys, is
kept out of the source code and is environment-specific.

For Example:

// environment.ts (Development)
export const environment = {
production: false,
apiUrl: 'https://dev.api.com',
};

// environment.prod.ts (Production)
export const environment = {
production: true,
apiUrl: 'https://prod.api.com',
};

// service.ts
import { environment } from '../environments/environment';

export class ApiService {


apiUrl = environment.apiUrl;
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
455

Explanation:
The correct environment file is applied based on the build environment, ensuring that
sensitive information is only used in the appropriate context.

67. Scenario: You need to create reusable templates for Vue components.

Question: How can Vue slots be used to create flexible templates?

Answer:
Slots in Vue allow components to accept dynamic content from the parent component.
They enable greater flexibility by letting the parent inject content into specific sections of the
child component’s template.

For Example:

<template>
<div>
<slot name="header"></slot>
<p>This is the default content.</p>
<slot></slot>
</div>
</template>

<!-- Usage -->


<MyComponent>
<template #header><h1>Header Content</h1></template>
<p>Additional content goes here.</p>
</MyComponent>

Explanation:
Slots allow the parent component to dynamically customize the content of the child
component.

68. Scenario: You need to maintain real-time communication between a


Node.js server and clients.

Question: How can WebSocket be implemented in Node.js for real-time communication?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
456

Answer:
WebSocket enables bidirectional communication between the server and clients. This
makes it ideal for real-time applications like chat apps, where both parties need to send and
receive data without repeatedly polling the server.

For Example:

const WebSocket = require('ws');


const server = new WebSocket.Server({ port: 8080 });

server.on('connection', (ws) => {


ws.send('Welcome!');
ws.on('message', (message) => console.log('Received:', message));
});

Explanation:
The server sends a welcome message when a client connects, and logs any messages
received from the client.

69. Scenario: You need to handle multiple asynchronous operations


sequentially in Node.js.

Question: How can you manage multiple promises using Promise.all() in Node.js?

Answer:
Promise.all() runs multiple promises in parallel and waits for all of them to resolve. If any
promise fails, the entire operation is rejected. This is useful for batch processing.

For Example:

const fetch = require('node-fetch');

async function fetchData() {


const [posts, users] = await Promise.all([
fetch('https://jsonplaceholder.typicode.com/posts').then((res) => res.json()),
fetch('https://jsonplaceholder.typicode.com/users').then((res) => res.json()),
]);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
457

console.log('Posts:', posts);
console.log('Users:', users);
}

fetchData();

Explanation:
This example fetches posts and users in parallel, reducing wait time.

70. Scenario: You want to create lazy-loaded modules in Angular for better
performance.

Question: How can Angular modules be lazy-loaded?

Answer:
Lazy loading in Angular defers the loading of modules until they are required, improving the
initial load time. This is configured through the loadChildren property in routes.

For Example:

const routes: Routes = [


{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then((m) => m.AdminModule),
},
];

Explanation:
The AdminModule is only loaded when the user navigates to the /admin route, optimizing
performance.

71. Scenario: You need to persist React state across browser reloads.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
458

Question: How can you use localStorage to persist state in a React application?

Answer:
In React, localStorage provides a way to store data in the browser's storage, which persists
even after the user reloads or closes the tab. This is useful for user preferences, shopping
carts, or form inputs that need to be retained between sessions. Using useState and
useEffect, you can initialize the state from localStorage and update the storage whenever
the state changes.

Detailed Steps:

1. Initialize state: When the component loads, use localStorage.getItem() to retrieve


the stored value. Use JSON parsing to handle structured data.
2. Update localStorage: Use localStorage.setItem() in a useEffect hook to save the
latest state whenever it changes.
3. Handle JSON safely: Since localStorage only stores strings, data needs to be
stringified and parsed properly.

For Example:

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

function Counter() {
const [count, setCount] = useState(() => {
const savedCount = localStorage.getItem('count');
return savedCount ? JSON.parse(savedCount) : 0;
});

useEffect(() => {
localStorage.setItem('count', JSON.stringify(count));
}, [count]);

return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
459

Explanation:

● State Initialization: When the component mounts, the useState hook tries to retrieve
the count from localStorage.
● Data Synchronization: useEffect ensures that every time the count changes, the
value is saved in localStorage.
This makes the state persistent, meaning the user will see the same counter value
even after refreshing the page.

72. Scenario: You need to debounce an input field to avoid excessive API
calls in React.

Question: How can you implement a debounced input field using setTimeout in React?

Answer:
Debouncing ensures that a function (such as an API call) only executes after a specified
period of inactivity. It’s useful in search inputs where users may type quickly, and calling the
API on every keystroke would be inefficient. By using setTimeout and clearTimeout, we can
delay the execution and cancel previous calls if the user continues typing.

Detailed Steps:

1. Use useState to track the input value and the debounced query separately.
2. In useEffect, set a timer that updates the debounced query after a delay (500ms).
3. Clear the timer when the component unmounts or when the input changes before
the timer completes.

For Example:

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

function DebouncedInput() {
const [query, setQuery] = useState('');
const [debouncedQuery, setDebouncedQuery] = useState(query);

useEffect(() => {
const handler = setTimeout(() => setDebouncedQuery(query), 500);
return () => clearTimeout(handler);
}, [query]);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
460

useEffect(() => {
if (debouncedQuery) {
console.log('API Call with:', debouncedQuery);
}
}, [debouncedQuery]);

return (
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
);
}

Explanation:

● Delaying API calls: The API is called only if the user stops typing for 500ms.
● Preventing redundant requests: Previous calls are cancelled using clearTimeout if
the user continues typing before the delay expires.
This approach improves performance and reduces server load.

73. Scenario: You need to manage deeply nested state in a Vuex store.

Question: How can Vuex modules help organize state in large Vue.js applications?

Answer:
As applications grow, managing large and deeply nested states becomes challenging. Vuex
modules allow developers to split the state into logical pieces based on the domain (e.g.,
user, products). Each module maintains its own state, mutations, actions, and getters,
promoting better organization.

Detailed Steps:

1. Create a Vuex module that encapsulates state logic for a specific domain (e.g., user).
2. Register the module inside the main Vuex store.
3. Access the module's state in components using map helpers or $store.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
461

For Example:

// store/user.js
export const user = {
state: () => ({
name: '',
email: ''
}),
mutations: {
setUser(state, user) {
state.name = user.name;
state.email = user.email;
}
}
};

// store/index.js
import { createStore } from 'vuex';
import { user } from './user';

export const store = createStore({


modules: {
user
}
});

Explanation:
The user module is isolated, making it easier to manage and maintain. Each module can
have its own logic, helping with modularization in larger Vue applications.

74. Scenario: You need to authenticate users in an Express.js API with JWT.

Question: How can you implement JWT authentication in an Express.js application?

Answer:
JWT (JSON Web Token) is a popular way to implement stateless authentication. Upon login,
the server creates a JWT token that contains encoded user information. This token is sent to
the client and included in subsequent requests for authorization.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
462

Detailed Steps:

1. Generate a JWT when the user logs in.


2. Verify the token for protected routes.
3. Store the token on the client-side (in localStorage or cookies).

For Example:

const express = require('express');


const jwt = require('jsonwebtoken');
const app = express();
const secret = 'your-secret-key';

app.post('/login', (req, res) => {


const user = { id: 1, name: 'Shardul' };
const token = jwt.sign(user, secret, { expiresIn: '1h' });
res.json({ token });
});

app.get('/protected', (req, res) => {


const token = req.headers['authorization'];
if (!token) return res.sendStatus(401);

jwt.verify(token, secret, (err, decoded) => {


if (err) return res.sendStatus(403);
res.json({ message: 'Protected content', user: decoded });
});
});

app.listen(3000, () => console.log('Server running on http://localhost:3000'));

Explanation:
The /login route creates a JWT token on successful login. The /protected route verifies the
token, ensuring the request is authorized.

75. Scenario: You need to set up lazy loading in Vue.js to improve


performance.

Question: How can lazy-loaded components be implemented in Vue.js?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
463

Answer:
In Vue.js, lazy loading defers the loading of components until they are needed. This
technique improves the initial load time by splitting the code into smaller chunks, loaded on
demand.

For Example:

const LazyComponent = () => import('./LazyComponent.vue');

export default {
components: {
LazyComponent
}
};

Explanation:
This example loads the LazyComponent only when it is required, enhancing performance by
reducing the initial bundle size.

76. Scenario: You need to implement role-based access control (RBAC) in


Angular.

Question: How can Angular guards be used to implement RBAC?

Answer:
Role-based access control (RBAC) restricts access to certain routes or components based on
the user's role. Angular provides route guards, such as CanActivate, to enforce this control
by checking if the user has the correct role before allowing access.

Detailed Steps:

1. Create a RoleGuard service using CanActivate.


2. Check the user's role in the guard before granting or denying access.
3. Apply the guard to the appropriate route in the router configuration.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
464

import { Injectable } from '@angular/core';


import { CanActivate } from '@angular/router';

@Injectable({
providedIn: 'root',
})
export class RoleGuard implements CanActivate {
canActivate(): boolean {
const userRole = 'admin'; // Assume this is fetched from user data
return userRole === 'admin'; // Allow only admins to access the route
}
}

Router Configuration:

const routes: Routes = [


{ path: 'admin', component: AdminComponent, canActivate: [RoleGuard] }
];

Explanation:
This guard ensures that only users with the admin role can access the /admin route. If the
user lacks the correct role, the route is blocked.

77. Scenario: You need to prevent memory leaks in a React component that
uses timers.

Question: How can useEffect cleanup functions prevent memory leaks?

Answer:
When a component uses timers or subscriptions, React’s useEffect cleanup function
ensures these resources are released when the component unmounts. This prevents
memory leaks, which could degrade performance.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
465

import React, { useEffect } from 'react';

function TimerComponent() {
useEffect(() => {
const timer = setInterval(() => console.log('Tick'), 1000);

// Cleanup function to clear the timer


return () => clearInterval(timer);
}, []);

return <div>Timer Running</div>;


}

export default TimerComponent;

Explanation:
The cleanup function returned by useEffect clears the timer when the component
unmounts, ensuring no unnecessary processes are left running.

78. Scenario: You need to monitor Node.js performance metrics.

Question: How can the process module help monitor performance in Node.js?

Answer:
Node.js provides the process module to monitor system metrics like memory usage and
uptime. This is useful for detecting memory leaks or analyzing the server's performance over
time.

For Example:

console.log('Memory Usage:', process.memoryUsage());


console.log('Uptime:', process.uptime());

Explanation:

● process.memoryUsage() returns an object with details about heap and RSS memory.
● process.uptime() shows how long the Node.js process has been running.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
466

These metrics help debug performance issues and ensure the application is optimized.

79. Scenario: You need to handle file uploads in Angular with progress
tracking.

Question: How can HttpClient be used to upload files with progress tracking?

Answer:
In Angular, the HttpClient service allows you to monitor file upload progress using the
reportProgress option. This helps provide user feedback during uploads.

For Example:

import { HttpClient, HttpEventType } from '@angular/common/http';

uploadFile(file: File) {
const formData = new FormData();
formData.append('file', file);

this.http.post('/upload', formData, {
reportProgress: true,
observe: 'events',
}).subscribe(event => {
if (event.type === HttpEventType.UploadProgress) {
const percentDone = Math.round((100 * event.loaded) / (event.total || 1));
console.log(`File is ${percentDone}% uploaded.`);
}
});
}

Explanation:
This example logs the upload progress to the console. Users get real-time feedback about
the upload status.

80. Scenario: You need to implement server-side rendering (SSR) in Vue.js.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
467

Question: How can SSR be set up for Vue.js using Nuxt.js?

Answer:
Server-side rendering (SSR) improves performance and SEO by rendering the page on the
server and sending fully-formed HTML to the browser. Nuxt.js simplifies SSR for Vue.js
applications.

For Example (Nuxt.js Page):

<template>
<div>
<h1>{{ message }}</h1>
</div>
</template>

<script>
export default {
data() {
return {
message: 'Hello from SSR!'
};
}
};
</script>

Explanation:
When a user requests this page, the server renders the HTML and sends it to the browser,
resulting in faster load times and improved SEO compared to client-side rendering.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
468

Chapter 9: Testing JavaScript Code

THEORETICAL QUESTIONS
1. What is Unit Testing in JavaScript?

Answer:
Unit testing focuses on verifying that small units of code—such as individual functions,
methods, or components—work as expected. The idea is to test each part of the code in
isolation, ensuring that every function behaves correctly for both typical and edge cases.
These tests are quick to run and help detect bugs early. Unit tests are also important for
regression testing, making sure that code changes don't accidentally introduce new bugs.

For Example:
A function like sum(a, b) is tested with various inputs to verify it behaves consistently:

function sum(a, b) {
return a + b;
}

test('adds 1 + 2 to equal 3', () => {


expect(sum(1, 2)).toBe(3);
});

The above Jest test ensures that the function returns 3 when adding 1 and 2. If the function
changes in the future, this test will catch any incorrect behavior.

2. What is Jest and how is it used for testing JavaScript?

Answer:
Jest is a popular testing framework for JavaScript, primarily used for unit testing. It provides
features like assertions, mocks, and test coverage out of the box. One reason Jest is widely
used is its simplicity and ease of integration, especially with React applications.

Jest is well-suited for both synchronous and asynchronous testing. It also offers snapshot
testing, which ensures that UI components don’t change unexpectedly over time. Jest can
execute tests in parallel, making it faster for large projects.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
469

For Example:

async function fetchData() {


return 'data fetched';
}

test('fetchData returns the expected value', async () => {


const data = await fetchData();
expect(data).toBe('data fetched');
});

This test demonstrates how Jest handles promises. The fetchData function is asynchronous,
and the test ensures it resolves to the correct value.

3. What is Mocha, and how does it differ from Jest?

Answer:
Mocha is another JavaScript testing framework, but unlike Jest, it is more flexible and
lightweight. Mocha only provides the test runner and doesn't include assertions or mocking
libraries by default. Developers often use it with Chai (for assertions) and Sinon (for mocks
and spies).

The main difference is that Jest is a complete testing suite with built-in features, while
Mocha offers more flexibility, making it useful for more complex testing scenarios where
customization is required.

For Example:

const { expect } = require('chai');

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

describe('Addition', () => {
it('should return 5 when adding 2 and 3', () => {
expect(add(2, 3)).to.equal(5);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
470

});
});

This Mocha test uses Chai to assert that the add function returns the correct value.

4. What is Integration Testing in JavaScript?

Answer:
Integration testing focuses on testing how different modules or components of an
application work together. It ensures that the interaction between these parts is functioning
correctly. For example, you might test the connection between your frontend and backend
or how your API interacts with the database.

For Example:

function getUser() {
return { id: 1, name: 'John Doe' };
}

test('should return the correct user object', () => {


const user = getUser();
expect(user).toEqual({ id: 1, name: 'John Doe' });
});

Here, we ensure that the service returns the expected user object. Although it’s simple,
integration tests for complex applications involve checking the behavior across multiple
layers, such as controllers interacting with services.

5. What is End-to-End (E2E) Testing, and when should it be used?

Answer:
End-to-End (E2E) testing validates the entire flow of an application, simulating real-world
user interactions. E2E tests are crucial for mission-critical user journeys like login, payment
processing, or checkout flows. They help ensure that every component of the system—from
the user interface to the backend services—works correctly.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
471

For Example:

describe('Login Test', () => {


it('should allow the user to log in and redirect to dashboard', () => {
cy.visit('https://example.com/login');
cy.get('#username').type('testuser');
cy.get('#password').type('password123');
cy.get('#loginButton').click();
cy.url().should('include', '/dashboard');
});
});

This Cypress test automates the login flow and verifies that the user is redirected to the
dashboard after successful login.

6. How can you debug test cases using Chrome DevTools?

Answer:
Chrome DevTools can be used to step through code and identify issues during tests. If you're
running tests with Jest, you can use the --inspect-brk flag to open a debugging session.
Chrome DevTools allows you to set breakpoints, inspect variables, and trace function calls,
making it easier to pinpoint errors.

For Example:
To debug a Jest test, run:

node --inspect-brk node_modules/.bin/jest

Then open Chrome DevTools by navigating to chrome://inspect. Attach to the running test
process and step through the code to debug.

7. What are mocks, and why are they used in testing?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
472

Answer:
Mocks are objects or functions that simulate the behavior of real dependencies, such as APIs
or databases, during testing. Mocks help isolate the code under test by providing controlled
responses. This ensures that tests are not affected by external factors, like network delays.

For Example:
Using Jest to mock a function:

const fetchData = jest.fn(() => 'mock data');

test('should return mock data', () => {


expect(fetchData()).toBe('mock data');
});

Here, fetchData is mocked to return 'mock data' instead of making an actual network call,
ensuring that the test runs quickly and reliably.

8. What is the role of assertions in testing?

Answer:
Assertions verify that the output of code matches the expected value. In JavaScript testing,
assertion libraries like Jest and Chai provide methods such as toBe, toEqual, and
toHaveLength to validate test results. If an assertion fails, the test will fail, highlighting the
error.

For Example:

test('2 + 2 equals 4', () => {


expect(2 + 2).toBe(4);
});

Here, the assertion ensures that the expression evaluates to 4. If it doesn’t, the test will fail,
alerting the developer to an issue.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
473

9. How do you set up Cypress for E2E testing?

Answer:
Cypress is a popular E2E testing tool that provides a complete framework with built-in
assertions and a GUI for running tests. Setting up Cypress involves installing it via npm and
running it through the Cypress test runner.

For Example:

npm install cypress --save-dev


npx cypress open

Create a test file under the cypress/integration directory:

describe('Home Page Test', () => {


it('should load the home page', () => {
cy.visit('https://example.com');
cy.contains('Welcome');
});
});

This test checks if the homepage loads and displays the text "Welcome".

10. What are the key differences between Unit, Integration, and E2E
Testing?

Answer:
These three types of testing serve different purposes in software development:

● Unit Testing: Tests individual functions or components in isolation.


● Integration Testing: Verifies that different modules work together correctly.
● E2E Testing: Simulates real user scenarios to validate the entire system.

Each type plays a role in ensuring software quality. Unit tests are fast and precise,
integration tests catch issues in component interaction, and E2E tests ensure the overall
user experience works smoothly.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
474

● Unit Test: Verify sum(a, b) returns the correct result.


● Integration Test: Ensure the user service returns the expected data from the
database.
● E2E Test: Automate a login flow to verify user redirection to the dashboard.

11. What is the importance of test coverage in JavaScript testing?

Answer:
Test coverage measures how much of your code is executed when running tests, ensuring
critical logic paths are not missed. It gives a percentage-based view of which statements,
branches, functions, and lines were covered during testing. While high coverage is not a
guarantee of a bug-free code, it reduces the chances of defects going unnoticed, especially
in complex systems.

Tools like Istanbul and Jest generate coverage reports, highlighting which parts of the code
were not executed. This ensures developers focus on writing more tests for uncovered logic.
However, test coverage should be used as a guideline, not a strict metric—100% coverage
doesn't always mean the code is fully tested, as tests can be poorly designed.

For Example:
Running coverage reports in Jest:

jest --coverage

The output will display metrics like statement coverage (85%) and branch coverage (75%),
allowing you to identify weak spots in testing.

12. How do you test asynchronous code in JavaScript?

Answer:
Testing asynchronous code can be tricky since the test runner needs to wait for promises to
resolve or callbacks to complete. Jest makes this easy by supporting async/await or
returning promises directly in test functions. If the asynchronous operation fails, the test will
automatically catch the failure. You can also use the done() callback to signal when an
asynchronous operation completes, but using async/await is preferred for readability.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
475

For Example:
Testing asynchronous code with async/await:

async function fetchData() {


return 'data loaded';
}

test('fetchData returns correct data', async () => {


const data = await fetchData();
expect(data).toBe('data loaded');
});

This test ensures that the fetchData function returns the correct value asynchronously.

13. What are test doubles, and how are they used in JavaScript?

Answer:
Test doubles are substitutes for real objects, allowing you to isolate the code under test from
its dependencies. They prevent tests from being affected by external systems such as
databases or APIs, ensuring repeatability and control. There are several types of test doubles:

● Mocks: Provide pre-configured responses.


● Stubs: Replace a function to control its behavior during tests.
● Spies: Track how a function is called without altering its behavior.

For Example:
Using a Jest spy:

const myFunction = jest.fn();

myFunction('test');
expect(myFunction).toHaveBeenCalledWith('test');

Here, the spy confirms that myFunction was called with 'test'.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
476

14. How do you test exceptions or errors in JavaScript?

Answer:
Testing for exceptions ensures that your code handles errors gracefully. In Jest, the toThrow
assertion verifies that a function raises a specific error when given invalid input. This type of
testing is crucial for robust error handling and ensuring that exceptions don't lead to system
crashes or unpredictable behavior.

For Example:
Testing a division function that throws an error:

function divide(a, b) {
if (b === 0) throw new Error('Division by zero');
return a / b;
}

test('throws error when dividing by zero', () => {


expect(() => divide(1, 0)).toThrow('Division by zero');
});

This test ensures that dividing by zero raises the appropriate error.

15. What is a test runner, and why is it used?

Answer:
A test runner automates the execution of test cases, providing results in a structured format.
Test runners streamline the process of running multiple tests and help monitor their
progress. They also integrate with CI/CD pipelines to run tests automatically after each code
commit, ensuring the application remains stable throughout development.

For Example:
Running tests with Jest:

npx jest

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
477

This command finds all test files (usually named *.test.js) and executes them, reporting
the results in the console.

16. How do you perform browser-based testing with Cypress?

Answer:
Cypress provides a framework for E2E testing by interacting with web pages in real browsers.
It simulates user actions (like clicks and form submissions) and verifies the application’s
behavior. Cypress also offers a visual GUI to run tests interactively, which is useful for
debugging.

For Example:
A simple Cypress test:

describe('Home Page Test', () => {


it('should display the welcome message', () => {
cy.visit('https://example.com');
cy.contains('Welcome').should('exist');
});
});

This test ensures that the homepage contains the word "Welcome".

17. How do you mock HTTP requests in JavaScript tests?

Answer:
Mocking HTTP requests is essential for testing components that rely on external APIs. It
ensures tests run independently of network conditions. By mocking fetch calls with Jest, you
can simulate server responses and verify how your code handles them.

For Example:
Mocking an API request:

global.fetch = jest.fn(() =>


Promise.resolve({

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
478

json: () => Promise.resolve({ data: 'mocked data' }),


})
);

test('fetches mocked data', async () => {


const response = await fetch('https://api.example.com/data');
const data = await response.json();
expect(data).toEqual({ data: 'mocked data' });
});

This test verifies that the component correctly handles the mocked API response.

18. How can you use setup and teardown methods in tests?

Answer:
Setup and teardown methods, such as beforeEach and afterEach, ensure that the testing
environment is consistent across tests. These methods initialize resources before each test
runs and clean up afterward, preventing test pollution (when one test affects another).

For Example:
Using setup and teardown with Jest:

let value;

beforeEach(() => {
value = 0; // Setup
});

afterEach(() => {
value = null; // Teardown
});

test('increments value', () => {


value += 1;
expect(value).toBe(1);
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
479

This ensures that each test starts with a fresh value and avoids side effects from other tests.

19. What is a snapshot test, and how is it used in JavaScript?

Answer:
Snapshot testing ensures that the UI or function output doesn’t change unexpectedly. It
captures the output of a component and saves it as a snapshot. When the test is run again,
the output is compared with the saved snapshot. If there are changes, the test fails,
prompting the developer to review and update the snapshot if necessary.

For Example:
Creating a snapshot with Jest:

function getMessage() {
return 'Hello, world!';
}

test('matches the snapshot', () => {


expect(getMessage()).toMatchSnapshot();
});

The first time this test runs, a snapshot is saved. Future runs compare the current output to
this snapshot.

20. What is Test-Driven Development (TDD) in JavaScript?

Answer:
Test-Driven Development (TDD) is a software development practice where developers write
tests before writing the actual code. The process involves three steps:

1. Write a failing test: Start by writing a test that specifies the desired functionality.
2. Write code to pass the test: Implement the simplest code to make the test pass.
3. Refactor: Improve the code while ensuring all tests remain green.

This practice ensures better code quality and encourages developers to think about edge
cases and requirements upfront.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
480

For Example:

1. Write a failing test:

test('adds 1 + 2 to equal 3', () => {


expect(add(1, 2)).toBe(3);
});

2. Write the code to pass the test:

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

3. Refactor if needed: Optimize the function without breaking the test.

TDD helps ensure that every piece of functionality is covered by tests, reducing the chances
of bugs.

21. How do you handle API response delays and timeouts in JavaScript
tests?

Answer:
In real-world scenarios, APIs might respond slowly, and testing such cases ensures the
application can gracefully handle delays or timeouts. To test for these scenarios, you can
mock delayed responses or timeouts in your tests. In Jest, you can use
jest.useFakeTimers() to simulate delayed operations without slowing down your tests.

For Example:
Simulating a delayed API response:

function fetchData() {
return new Promise((resolve) =>

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
481

setTimeout(() => resolve('data fetched'), 3000)


);
}

test('resolves with correct data after delay', async () => {


jest.useFakeTimers();
const dataPromise = fetchData();
jest.runAllTimers(); // Simulates the passage of time
const data = await dataPromise;
expect(data).toBe('data fetched');
});

This test simulates a delayed response and ensures the correct behavior is verified without
actually waiting for 3 seconds.

22. How do you ensure test isolation when dealing with shared states in
JavaScript?

Answer:
Test isolation ensures that the outcome of one test does not affect other tests. When tests
share state (e.g., global variables or modules), it can lead to flaky tests (tests that fail
intermittently). To ensure isolation, each test should either reset state after running or use
mock objects that are unique to the test.

For Example:
Using beforeEach to reset a shared state:

let counter;

beforeEach(() => {
counter = 0; // Reset state before each test
});

test('increments the counter', () => {


counter++;
expect(counter).toBe(1);
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
482

test('increments the counter again', () => {


counter++;
expect(counter).toBe(1); // Ensured isolation
});

This approach ensures each test starts with a fresh counter value.

23. What is the significance of mocking modules in JavaScript testing?

Answer:
Mocking modules allows you to replace an entire module with a mock version during
testing. This is useful when you want to isolate the code under test from external
dependencies, such as database modules or third-party libraries. Jest provides a simple way
to mock modules with jest.mock().

For Example:
Mocking a module:

// userService.js
module.exports = () => 'Real User';

// userService.test.js
jest.mock('./userService', () => () => 'Mocked User');

const getUser = require('./userService');

test('returns mocked user', () => {


expect(getUser()).toBe('Mocked User');
});

Here, the userService module is mocked to return a custom value during the test.

24. How can you test React components using Jest and React Testing
Library?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
483

Answer:
Testing React components involves verifying that they render correctly and behave as
expected under different conditions. The React Testing Library works with Jest to render
components in a lightweight environment and provide utilities to query the DOM.

For Example:
Testing a React component:

import { render, screen } from '@testing-library/react';


import '@testing-library/jest-dom'; // Provides custom matchers
import Greeting from './Greeting';

test('renders greeting message', () => {


render(<Greeting name="Shardul" />);
expect(screen.getByText('Hello, Shardul!')).toBeInTheDocument();
});

This test ensures the component renders the correct greeting message based on the name
prop.

25. What is parameterized testing, and how is it used in JavaScript?

Answer:
Parameterized tests allow you to run the same test logic with multiple inputs and expected
outputs. This reduces code duplication and ensures comprehensive testing across different
scenarios. Jest supports parameterized tests through the test.each method.

For Example:
Using parameterized tests:

test.each([
[1, 2, 3],
[2, 3, 5],
[3, 5, 8],
])('adds %i and %i to equal %i', (a, b, expected) => {
expect(a + b).toBe(expected);
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
484

This test runs three different scenarios using the same test logic.

26. How do you test middleware functions in Express applications?

Answer:
Middleware in Express is used to handle requests and responses before they reach the route
handlers. When testing middleware, it is essential to verify that it behaves correctly for
various request scenarios.

For Example:
Testing a middleware function:

const myMiddleware = (req, res, next) => {


req.user = { name: 'Shardul' };
next();
};

test('adds user to request object', () => {


const req = {};
const res = {};
const next = jest.fn();

myMiddleware(req, res, next);

expect(req.user).toEqual({ name: 'Shardul' });


expect(next).toHaveBeenCalled();
});

This test ensures that the middleware correctly modifies the req object and calls the next
function.

27. How do you use Jest timers to test functions with delays?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
485

Answer:
Jest provides fake timers to control and manipulate time-based operations. This feature is
useful for testing functions with setTimeout, setInterval, or any delayed logic without
slowing down your tests.

For Example:
Using Jest fake timers:

function delayedFunction(callback) {
setTimeout(() => callback('done'), 1000);
}

test('calls callback after delay', () => {


jest.useFakeTimers();
const callback = jest.fn();

delayedFunction(callback);
jest.runAllTimers();

expect(callback).toHaveBeenCalledWith('done');
});

This test ensures the callback is invoked correctly after the simulated delay.

28. How do you use spies to test function calls and arguments in
JavaScript?

Answer:
Spies allow you to monitor how a function is called during a test, including the number of
calls and the arguments used. Jest spies can wrap existing functions or be created as
standalone mocks.

For Example:
Using a spy in Jest:

const originalFunction = jest.fn((x) => x + 1);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
486

test('calls original function with correct argument', () => {


originalFunction(2);
expect(originalFunction).toHaveBeenCalledWith(2);
expect(originalFunction).toHaveReturnedWith(3);
});

This test ensures the function was called with the correct argument and returned the
expected value.

29. What is the purpose of global setup and teardown in testing?

Answer:
Global setup and teardown functions run before and after all tests in a test suite. These
functions are used to initialize resources (like databases) or clean up after tests complete.
Jest provides hooks like beforeAll and afterAll for global setup and teardown.

For Example:
Using global setup and teardown:

beforeAll(() => {
console.log('Setting up resources');
});

afterAll(() => {
console.log('Cleaning up resources');
});

test('dummy test', () => {


expect(1 + 1).toBe(2);
});

This ensures that setup and cleanup happen exactly once for all tests.

30. How do you generate code coverage reports and interpret them?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
487

Answer:
Generating code coverage reports helps you understand which parts of the codebase are
being tested. Jest includes built-in tools to generate such reports. These reports display the
percentage of code covered across statements, branches, and functions.

For Example:
Generating a coverage report:

jest --coverage

The report will look like this:

File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s


-------------------|---------|----------|---------|---------|-------------------
All files | 85.71 | 75.00 | 100.00 | 85.71 |
sum.js | 100.00 | 50.00 | 100.00 | 100.00 | 10

This report highlights areas of code that need additional tests, helping developers prioritize
testing efforts.

31. How do you test a Redux store and actions in JavaScript applications?

Answer:
Testing a Redux store involves verifying that actions trigger the correct state changes.
Actions represent events or user interactions, while the store holds the application’s state. For
Redux, it’s essential to ensure:

● Actions return the correct payload.


● Reducers update the state correctly.
● Middleware (if any) behaves as expected.

For Example:
Testing Redux actions and reducers with Jest:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
488

// actions.js
const increment = () => ({ type: 'INCREMENT' });

// reducer.js
const reducer = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1;
default:
return state;
}
};

// actions.test.js
test('increment action returns correct payload', () => {
expect(increment()).toEqual({ type: 'INCREMENT' });
});

// reducer.test.js
test('reducer increments state', () => {
expect(reducer(0, { type: 'INCREMENT' })).toBe(1);
});

This test ensures that actions return the correct type and the reducer updates the state
properly.

32. How do you mock Axios or Fetch calls in JavaScript tests?

Answer:
When testing code that makes HTTP requests using Axios or the native fetch() API, it’s
important to mock these calls to avoid actual network requests. Jest provides utilities to
mock Axios or fetch calls, ensuring tests are fast and isolated.

For Example:
Mocking an Axios request:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
489

import axios from 'axios';


jest.mock('axios');

test('fetches user data successfully', async () => {


const mockData = { data: { name: 'Shardul' } };
axios.get.mockResolvedValue(mockData);

const response = await axios.get('/user');


expect(response.data.name).toBe('Shardul');
});

This test ensures that the mocked Axios request returns the expected data.

33. How do you test WebSocket connections in JavaScript?

Answer:
WebSocket connections allow real-time communication between the server and client. To
test WebSocket behavior, you can mock WebSocket instances and simulate message
events. The tests ensure the client responds correctly to messages or connection changes.

For Example:
Mocking WebSocket:

test('handles WebSocket message correctly', () => {


const mockWebSocket = new WebSocket('ws://localhost');
const onMessage = jest.fn();

mockWebSocket.onmessage = onMessage;
mockWebSocket.onmessage({ data: 'Hello' });

expect(onMessage).toHaveBeenCalledWith({ data: 'Hello' });


});

This test verifies that the WebSocket message handler processes messages correctly.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
490

34. How do you test custom hooks in React applications?

Answer:
Testing custom React hooks ensures they behave correctly when state or props change.
React Testing Library provides utilities like renderHook() to test hooks independently.

For Example:
Testing a custom hook:

import { renderHook, act } from '@testing-library/react-hooks';


import { useState } from 'react';

function useCounter() {
const [count, setCount] = useState(0);
const increment = () => setCount((prev) => prev + 1);
return { count, increment };
}

test('should increment counter', () => {


const { result } = renderHook(() => useCounter());

act(() => result.current.increment());


expect(result.current.count).toBe(1);
});

This test ensures that the hook increments the counter as expected.

35. How do you handle race conditions in asynchronous tests?

Answer:
Race conditions occur when multiple asynchronous operations complete at unpredictable
times, potentially leading to incorrect behavior. Tests should ensure that code handles race
conditions gracefully by properly waiting for promises or using Promise.all().

For Example:
Testing with Promise.all():

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
491

function fetchData(url) {
return new Promise((resolve) => setTimeout(() => resolve(url), 100));
}

test('resolves all promises in order', async () => {


const results = await Promise.all([fetchData('url1'), fetchData('url2')]);
expect(results).toEqual(['url1', 'url2']);
});

This ensures the promises resolve as expected, avoiding race condition issues.

36. How do you test performance using JavaScript?

Answer:
Performance testing ensures that code runs efficiently under different conditions. You can
use Node.js performance hooks or browser APIs like performance.now() to measure
execution times.

For Example:
Measuring performance in a function:

test('performance of function', () => {


const start = performance.now();

// Function to be tested
for (let i = 0; i < 1000000; i++) {}

const end = performance.now();


console.log(`Execution time: ${end - start}ms`);
});

This test logs the time taken to execute a loop.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
492

37. How do you test for memory leaks in JavaScript applications?

Answer:
Memory leaks occur when unused memory is not released, causing the application to
consume more memory over time. In Node.js, you can use tools like heap snapshots and
node-memwatch to monitor memory usage during tests.

For Example:
Using global.gc() to force garbage collection:

test('detect memory leaks', () => {


const usedBefore = process.memoryUsage().heapUsed;
const arr = new Array(100000).fill(0);
global.gc(); // Trigger garbage collection
const usedAfter = process.memoryUsage().heapUsed;

expect(usedAfter).toBeGreaterThan(usedBefore);
});

This test helps detect memory growth over time.

38. How do you perform cross-browser testing in JavaScript?

Answer:
Cross-browser testing ensures the application behaves consistently across different browsers.
Tools like Selenium, Cypress, or Playwright automate browser interactions for testing
compatibility.

For Example:
Using Playwright to test cross-browser behavior:

const { chromium, firefox, webkit } = require('playwright');

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
493

test('works in multiple browsers', async () => {


for (const browserType of [chromium, firefox, webkit]) {
const browser = await browserType.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
expect(await page.title()).toBe('Example Domain');
await browser.close();
}
});

This test verifies that the page loads correctly in multiple browsers.

39. How do you test GraphQL APIs in JavaScript?

Answer:
Testing GraphQL APIs ensures the queries and mutations behave as expected. You can use
tools like Apollo Client with Jest to simulate GraphQL operations.

For Example:
Testing a GraphQL query:

import { gql } from '@apollo/client';

const GET_USER = gql`


query GetUser {
user {
name
}
}
`;

test('fetches user data', async () => {


const mockClient = createMockClient({
request: { query: GET_USER },
result: { data: { user: { name: 'Shardul' } } },
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
494

const result = await mockClient.query({ query: GET_USER });


expect(result.data.user.name).toBe('Shardul');
});

This test ensures the GraphQL query returns the expected data.

40. How do you test accessibility (a11y) in JavaScript applications?

Answer:
Accessibility testing ensures your application is usable by people with disabilities. Tools like
axe-core can be used with Jest to test for accessibility violations.

For Example:
Using axe-core for accessibility testing:

import { render } from '@testing-library/react';


import { toHaveNoViolations } from 'jest-axe';
import axe from 'axe-core';

expect.extend(toHaveNoViolations);

test('accessibility check', async () => {


const { container } = render(<button>Click me</button>);
const results = await axe.run(container);
expect(results).toHaveNoViolations();
});

This test ensures the rendered component has no accessibility violations.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
495

SCENARIO QUESTIONS
41. Scenario: Testing a function that adds two numbers using Jest

Question: How would you write a Jest unit test for a simple addition function?

Answer:
Unit tests focus on small, isolated pieces of code, such as individual functions. To test an
addition function with Jest, you need to ensure the function returns the expected sum for
various inputs.

For Example:

// add.js
function add(a, b) {
return a + b;
}

module.exports = add;

// add.test.js
const add = require('./add');

test('adds 1 + 2 to equal 3', () => {


expect(add(1, 2)).toBe(3);
});

test('adds -1 + 1 to equal 0', () => {


expect(add(-1, 1)).toBe(0);
});

In this example, two test cases ensure that the add function behaves correctly for both
positive and negative numbers.

42. Scenario: Verifying asynchronous code using Jest's async/await

Question: How do you test an asynchronous function that fetches data using Jest?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
496

Answer:
Testing asynchronous functions ensures that promises resolve or reject as expected. With
Jest, you can use async/await to wait for the function’s result and verify its correctness.

For Example:

// fetchData.js
async function fetchData() {
return 'data loaded';
}

module.exports = fetchData;

// fetchData.test.js
const fetchData = require('./fetchData');

test('fetchData resolves with "data loaded"', async () => {


const data = await fetchData();
expect(data).toBe('data loaded');
});

This test waits for fetchData() to resolve and ensures it returns the expected string.

43. Scenario: Mocking a dependency in a JavaScript module using Jest

Question: How would you mock a dependency using Jest when testing a function?

Answer:
Sometimes functions depend on external modules, such as APIs. You can mock those
dependencies to isolate your tests and control their behavior.

For Example:

// getUser.js
const fetch = require('node-fetch');

async function getUser() {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
497

const response = await fetch('https://api.example.com/user');


return response.json();
}

module.exports = getUser;

// getUser.test.js
const getUser = require('./getUser');
jest.mock('node-fetch', () => jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ name: 'Shardul' }),
})
));

test('returns user data', async () => {


const user = await getUser();
expect(user.name).toBe('Shardul');
});

Here, the fetch function is mocked to return predefined user data.

44. Scenario: Integration testing with multiple functions interacting


together

Question: How do you write an integration test for two functions working together?

Answer:
Integration tests ensure that multiple functions or modules work as expected when used
together.

For Example:

// mathOperations.js
function add(a, b) {
return a + b;
}

function multiply(a, b) {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
498

return a * b;
}

module.exports = { add, multiply };

// mathOperations.test.js
const { add, multiply } = require('./mathOperations');

test('adds 2 + 3 and multiplies result by 2', () => {


const sum = add(2, 3);
const product = multiply(sum, 2);
expect(product).toBe(10);
});

This integration test ensures that both the add and multiply functions interact correctly.

45. Scenario: Testing a user login flow using Cypress

Question: How would you test a login flow on a web application using Cypress?

Answer:
End-to-end tests verify that complete user flows work as intended. In this case, Cypress
simulates the login process and ensures the user lands on the correct page.

For Example:

describe('Login Test', () => {


it('logs the user in and redirects to the dashboard', () => {
cy.visit('https://example.com/login');
cy.get('#username').type('testuser');
cy.get('#password').type('password123');
cy.get('#loginButton').click();
cy.url().should('include', '/dashboard');
});
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
499

This Cypress test automates the login process and ensures the user is redirected to the
dashboard.

46. Scenario: Debugging failed tests using Chrome DevTools

Question: How do you use Chrome DevTools to debug failed Jest tests?

Answer:
You can run Jest with the --inspect-brk flag and attach Chrome DevTools to debug failed
tests step by step.

For Example:

Run Jest in debug mode:

node --inspect-brk node_modules/.bin/jest

1.
2. Open Chrome DevTools by navigating to chrome://inspect.
3. Click on "Inspect" next to the Node.js process.
4. Set breakpoints and step through the test code to identify issues.

47. Scenario: Handling API failures in tests

Question: How do you test a function to ensure it handles API failures correctly?

Answer:
It’s essential to verify that your code handles API errors gracefully. You can mock an API call
to reject with an error and ensure the function responds correctly.

For Example:

const fetch = require('node-fetch');


jest.mock('node-fetch');

const fetchData = async () => {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
500

try {
const response = await fetch('https://api.example.com/data');
return response.json();
} catch (error) {
return 'API Error';
}
};

test('returns "API Error" on failure', async () => {


fetch.mockRejectedValue(new Error('Network error'));
const result = await fetchData();
expect(result).toBe('API Error');
});

This test ensures the function returns "API Error" when the fetch request fails.

48. Scenario: Testing middleware in an Express.js application

Question: How do you test a middleware function in Express using Jest?

Answer:
Middleware functions process requests before passing them to route handlers. Testing
middleware ensures it behaves correctly when modifying the req or res objects.

For Example:

const myMiddleware = (req, res, next) => {


req.user = { name: 'Shardul' };
next();
};

test('adds user to request object', () => {


const req = {};
const res = {};
const next = jest.fn();

myMiddleware(req, res, next);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
501

expect(req.user).toEqual({ name: 'Shardul' });


expect(next).toHaveBeenCalled();
});

This test verifies that the middleware adds a user object to the request.

49. Scenario: Testing a button click handler with Jest

Question: How do you test a button click handler using Jest?

Answer:
Button click handlers trigger specific actions. It’s important to test these handlers to ensure
they behave correctly when invoked.

For Example:

function handleClick(callback) {
callback('Button clicked');
}

test('calls callback with correct message', () => {


const callback = jest.fn();
handleClick(callback);
expect(callback).toHaveBeenCalledWith('Button clicked');
});

This test ensures that the click handler triggers the callback with the correct message.

50. Scenario: Simulating network delays in Cypress tests

Question: How do you simulate a slow network in a Cypress test?

Answer:
Simulating network delays in Cypress helps test how the UI behaves under slow network
conditions. You can use the cy.intercept() method to control response timings.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
502

For Example:

describe('Simulate slow network', () => {


it('displays loading spinner during slow response', () => {
cy.intercept('GET', '/api/data', {
delay: 5000, // Simulate 5-second delay
body: { data: 'Sample data' },
}).as('getData');

cy.visit('https://example.com');
cy.get('#loadingSpinner').should('be.visible');
cy.wait('@getData');
cy.get('#loadingSpinner').should('not.exist');
});
});

This test verifies that a loading spinner is displayed during a simulated network delay.

51. Scenario: Writing a unit test with Mocha and Chai

Question: How do you write a unit test using Mocha and Chai for a function that checks if a
number is even?

Answer:
Mocha is a JavaScript test runner that allows you to structure tests, while Chai provides
assertion methods to verify outcomes. In this scenario, we’ll test a function that checks if a
number is even. Mocha will run the test, and Chai will assert that the function behaves
correctly.

For Example:

// even.js
function isEven(num) {
return num % 2 === 0;
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
503

module.exports = isEven;

// even.test.js
const { expect } = require('chai');
const isEven = require('./even');

describe('isEven', () => {
it('should return true for even numbers', () => {
expect(isEven(4)).to.be.true;
});

it('should return false for odd numbers', () => {


expect(isEven(3)).to.be.false;
});
});

This test ensures that the isEven function returns true for even numbers and false for odd
ones.

52. Scenario: Handling and testing errors with Mocha

Question: How do you test that a function throws an error using Mocha and Chai?

Answer:
Testing error handling is essential to ensure that your code behaves correctly under failure
scenarios. Mocha and Chai allow you to assert that a function throws an expected error
when provided with invalid input.

For Example:

// divide.js
function divide(a, b) {
if (b === 0) throw new Error('Division by zero');
return a / b;
}

module.exports = divide;

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
504

// divide.test.js
const { expect } = require('chai');
const divide = require('./divide');

describe('divide', () => {
it('should throw an error when dividing by zero', () => {
expect(() => divide(4, 0)).to.throw('Division by zero');
});

it('should return the correct result for valid inputs', () => {


expect(divide(4, 2)).to.equal(2);
});
});

This test verifies that dividing by zero throws the correct error and valid inputs return the
expected result.

53. Scenario: Testing API endpoints with Mocha and Supertest

Question: How do you test an Express API endpoint using Mocha and Supertest?

Answer:
Supertest allows you to test Express endpoints by making simulated HTTP requests. You can
use it with Mocha to ensure that your API behaves as expected under different scenarios.

For Example:

// server.js
const express = require('express');
const app = express();

app.get('/status', (req, res) => {


res.status(200).json({ status: 'OK' });
});

module.exports = app;

// server.test.js

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
505

const request = require('supertest');


const app = require('./server');

describe('GET /status', () => {


it('should return status OK', (done) => {
request(app)
.get('/status')
.expect('Content-Type', /json/)
.expect(200, { status: 'OK' }, done);
});
});

This test ensures that the /status endpoint returns the correct response and status code.

54. Scenario: Testing a counter component with React Testing Library

Question: How do you test a React component that increments a counter using React
Testing Library?

Answer:
Testing React components ensures they respond correctly to user interactions. In this
example, we’ll test a simple counter component that increments when a button is clicked.

For Example:

// Counter.js
import React, { useState } from 'react';

function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
506

export default Counter;

// Counter.test.js
import { render, screen, fireEvent } from '@testing-library/react';
import Counter from './Counter';

test('increments counter when button is clicked', () => {


render(<Counter />);
fireEvent.click(screen.getByText('Increment'));
expect(screen.getByText('Count: 1')).toBeInTheDocument();
});

This test ensures that clicking the button increments the counter.

55. Scenario: Mocking a timer function with Jest fake timers

Question: How do you test a function that uses setTimeout with Jest fake timers?

Answer:
Jest’s fake timers allow you to simulate the passage of time in tests, making it easier to test
functions that rely on timeouts or intervals.

For Example:

// delayedMessage.js
function delayedMessage(callback) {
setTimeout(() => {
callback('Hello after 1 second');
}, 1000);
}

module.exports = delayedMessage;

// delayedMessage.test.js
const delayedMessage = require('./delayedMessage');

test('calls callback after 1 second', () => {


jest.useFakeTimers();

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
507

const callback = jest.fn();

delayedMessage(callback);
jest.runAllTimers();

expect(callback).toHaveBeenCalledWith('Hello after 1 second');


});

This test ensures that the callback is called with the correct message after the simulated
delay.

56. Scenario: Testing user input validation in a form component

Question: How do you test input validation logic in a React form component?

Answer:
Testing input validation ensures that the form behaves correctly when users enter valid or
invalid data.

For Example:

// LoginForm.js
import React, { useState } from 'react';

function LoginForm() {
const [error, setError] = useState('');

const handleSubmit = (event) => {


event.preventDefault();
const username = event.target.username.value;
if (!username) setError('Username is required');
};

return (
<form onSubmit={handleSubmit}>
<input name="username" />
<button type="submit">Submit</button>
{error && <p>{error}</p>}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
508

</form>
);
}

export default LoginForm;

// LoginForm.test.js
import { render, fireEvent, screen } from '@testing-library/react';
import LoginForm from './LoginForm';

test('displays error when username is empty', () => {


render(<LoginForm />);
fireEvent.click(screen.getByText('Submit'));
expect(screen.getByText('Username is required')).toBeInTheDocument();
});

This test ensures the form displays an error if the username input is left empty.

57. Scenario: Testing API request retries on failure

Question: How do you test that an API request is retried on failure?

Answer:
Testing retry logic ensures that network requests are retried when the initial attempt fails.

For Example:

// fetchWithRetry.js
async function fetchWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url);
if (response.ok) return response.json();
} catch (error) {
if (i === retries - 1) throw error;
}
}
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
509

module.exports = fetchWithRetry;

// fetchWithRetry.test.js
global.fetch = jest.fn(() =>
Promise.reject(new Error('Network error'))
);

test('retries API request 3 times on failure', async () => {


const fetchWithRetry = require('./fetchWithRetry');

await expect(fetchWithRetry('https://api.example.com')).rejects.toThrow('Network
error');
expect(fetch).toHaveBeenCalledTimes(3);
});

This test ensures the request is retried three times on failure.

58. Scenario: Testing state changes with React hooks

Question: How do you test a custom hook that manages state in React?

Answer:
Testing hooks ensures that state management logic behaves as expected.

For Example:

// useCounter.js
import { useState } from 'react';

export function useCounter() {


const [count, setCount] = useState(0);
const increment = () => setCount((prev) => prev + 1);
return { count, increment };
}

// useCounter.test.js
import { renderHook, act } from '@testing-library/react-hooks';

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
510

import { useCounter } from './useCounter';

test('increments counter state', () => {


const { result } = renderHook(() => useCounter());

act(() => result.current.increment());


expect(result.current.count).toBe(1);
});

This test verifies the increment function updates the state correctly.

59. Scenario: Handling edge cases in Jest tests

Question: How do you test a function with edge cases using Jest?

Answer:
Testing edge cases ensures your code behaves correctly for unusual inputs.

For Example:

function reverseString(str) {
if (!str) return '';
return str.split('').reverse().join('');
}

test('returns empty string for null input', () => {


expect(reverseString(null)).toBe('');
});

test('reverses non-empty string', () => {


expect(reverseString('hello')).toBe('olleh');
});

This test ensures the function handles both normal and edge cases correctly.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
511

60. Scenario: Testing CSS class changes based on state in React

Question: How do you test that CSS classes change based on component state in React?

Answer:
Testing CSS class changes ensures that UI elements respond to state changes as expected.

For Example:

// ToggleButton.js
import React, { useState } from 'react';

function ToggleButton() {
const [active, setActive] = useState(false);
return (
<button className={active ? 'active' : ''} onClick={() => setActive(!active)}>
Toggle
</button>
);
}

export default ToggleButton;

// ToggleButton.test.js
import { render, fireEvent, screen } from '@testing-library/react';
import ToggleButton from './ToggleButton';

test('toggles CSS class on click', () => {


render(<ToggleButton />);
const button = screen.getByRole('button');

fireEvent.click(button);
expect(button).toHaveClass('active');

fireEvent.click(button);
expect(button).not.toHaveClass('active');
});

This test ensures the button's CSS class toggles based on the active state.

61. Scenario: Mocking multiple dependencies in Jest for complex testing

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
512

Question: How do you mock multiple dependencies in a JavaScript function using Jest?

Answer:
When a function depends on multiple modules, mocking them allows you to isolate the
function under test. This ensures your test is not affected by changes or failures in external
modules.

For Example:

// userService.js
const getUser = require('./getUser');
const sendEmail = require('./sendEmail');

async function notifyUser(userId) {


const user = await getUser(userId);
return sendEmail(user.email, 'Notification sent');
}

module.exports = notifyUser;

// userService.test.js
jest.mock('./getUser', () => jest.fn(() => ({ email: '[email protected]' })));
jest.mock('./sendEmail', () => jest.fn());

const notifyUser = require('./userService');


const sendEmail = require('./sendEmail');

test('sends notification email', async () => {


await notifyUser(1);
expect(sendEmail).toHaveBeenCalledWith('[email protected]', 'Notification sent');
});

This test ensures the function interacts correctly with both dependencies.

62. Scenario: Testing Redux async actions with Thunk middleware

Question: How do you test Redux async actions using Thunk middleware?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
513

Answer:
Testing async actions ensures they dispatch the correct actions in the expected order. Redux
Thunk allows actions to return functions, making it possible to handle asynchronous logic.

For Example:

// actions.js
export const fetchData = () => async (dispatch) => {
dispatch({ type: 'FETCH_START' });
try {
const data = await fetch('/api/data').then((res) => res.json());
dispatch({ type: 'FETCH_SUCCESS', payload: data });
} catch {
dispatch({ type: 'FETCH_ERROR' });
}
};

// actions.test.js
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { fetchData } from './actions';

const mockStore = configureMockStore([thunk]);

test('dispatches correct actions on success', async () => {


global.fetch = jest.fn(() =>
Promise.resolve({ json: () => Promise.resolve({ data: 'Test data' }) })
);

const store = mockStore();


await store.dispatch(fetchData());

expect(store.getActions()).toEqual([
{ type: 'FETCH_START' },
{ type: 'FETCH_SUCCESS', payload: { data: 'Test data' } },
]);
});

This test ensures that the correct actions are dispatched during the async flow.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
514

63. Scenario: Simulating user events with Cypress across multiple pages

Question: How do you test a multi-page flow in a web application using Cypress?

Answer:
E2E tests using Cypress ensure the entire user journey works as expected across multiple
pages. This includes checking for navigation, input handling, and state persistence between
pages.

For Example:

describe('Multi-page flow', () => {


it('completes a user registration', () => {
cy.visit('/register');
cy.get('#username').type('testuser');
cy.get('#nextButton').click();
cy.url().should('include', '/details');
cy.get('#email').type('[email protected]');
cy.get('#submitButton').click();
cy.url().should('include', '/confirmation');
cy.contains('Registration successful');
});
});

This test verifies that the registration flow works across multiple pages.

64. Scenario: Testing file uploads in Cypress

Question: How do you test a file upload feature using Cypress?

Answer:
Testing file uploads ensures that the system can correctly handle and process user-uploaded
files.

For Example:

describe('File upload test', () => {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
515

it('uploads a file successfully', () => {


cy.visit('/upload');
cy.get('input[type="file"]').attachFile('test-file.txt');
cy.get('#uploadButton').click();
cy.contains('Upload successful');
});
});

This test ensures that the upload process works as expected.

65. Scenario: Testing protected routes in an Express.js application

Question: How do you test that only authenticated users can access protected routes?

Answer:
Testing protected routes ensures that only authenticated users can access certain endpoints.

For Example:

// authMiddleware.js
function authMiddleware(req, res, next) {
if (req.isAuthenticated()) {
next();
} else {
res.status(401).send('Unauthorized');
}
}

module.exports = authMiddleware;

// authMiddleware.test.js
const request = require('supertest');
const express = require('express');
const authMiddleware = require('./authMiddleware');

const app = express();


app.get('/protected', authMiddleware, (req, res) => res.send('Protected content'));

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
516

test('blocks unauthorized access', async () => {


await request(app).get('/protected').expect(401, 'Unauthorized');
});

This test ensures that unauthorized users cannot access protected routes.

66. Scenario: Testing WebSocket reconnections

Question: How do you test WebSocket reconnection logic?

Answer:
Testing WebSocket reconnections ensures that the system can reconnect automatically
when the connection is lost.

For Example:

function connectWebSocket(url, onMessage) {


let socket = new WebSocket(url);

socket.onclose = () => {
setTimeout(() => connectWebSocket(url, onMessage), 1000);
};

socket.onmessage = onMessage;
return socket;
}

test('reconnects after disconnection', () => {


jest.useFakeTimers();
const onMessage = jest.fn();
const socket = connectWebSocket('ws://localhost', onMessage);

socket.onclose();
jest.runAllTimers();
expect(onMessage).not.toHaveBeenCalled(); // Verify no message received on
reconnect
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
517

This test ensures the WebSocket reconnects after a disconnection.

67. Scenario: Testing animations with Jest and React Testing Library

Question: How do you test CSS animations triggered by state changes in React?

Answer:
Testing animations ensures that state changes trigger the correct CSS classes.

For Example:

// FadeComponent.js
import React, { useState } from 'react';

function FadeComponent() {
const [visible, setVisible] = useState(false);
return (
<div>
<button onClick={() => setVisible(!visible)}>Toggle</button>
<div className={visible ? 'fade-in' : 'fade-out'}>Content</div>
</div>
);
}

export default FadeComponent;

// FadeComponent.test.js
import { render, screen, fireEvent } from '@testing-library/react';
import FadeComponent from './FadeComponent';

test('applies correct animation class on toggle', () => {


render(<FadeComponent />);
const button = screen.getByText('Toggle');
const content = screen.getByText('Content');

fireEvent.click(button);
expect(content).toHaveClass('fade-in');

fireEvent.click(button);
expect(content).toHaveClass('fade-out');

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
518

});

This test ensures the correct animation class is applied based on state.

68. Scenario: Testing React Context API state management

Question: How do you test state changes in components that use the React Context API?

Answer:
Testing components that consume React Context ensures they respond correctly to state
changes from the context.

For Example:

// ThemeContext.js
import React, { createContext, useContext, useState } from 'react';

const ThemeContext = createContext();

export function ThemeProvider({ children }) {


const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}

export function useTheme() {


return useContext(ThemeContext);
}

// ThemeToggle.js
import React from 'react';
import { useTheme } from './ThemeContext';

function ThemeToggle() {
const { theme, setTheme } = useTheme();

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
519

return (
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle Theme
</button>
);
}

export default ThemeToggle;

// ThemeToggle.test.js
import { render, fireEvent } from '@testing-library/react';
import { ThemeProvider } from './ThemeContext';
import ThemeToggle from './ThemeToggle';

test('toggles theme between light and dark', () => {


const { getByText } = render(
<ThemeProvider>
<ThemeToggle />
</ThemeProvider>
);

const button = getByText('Toggle Theme');


fireEvent.click(button); // Changes theme to 'dark'
fireEvent.click(button); // Changes theme back to 'light'
});

This test verifies that clicking the button toggles the theme correctly.

69. Scenario: Testing custom event listeners in JavaScript

Question: How do you test a custom event listener for DOM elements?

Answer:
Testing custom event listeners ensures correct interaction handling with DOM elements.

For Example:

function addCustomListener(element, callback) {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
520

element.addEventListener('customEvent', callback);
}

// Test
test('calls callback on custom event', () => {
const element = document.createElement('div');
const callback = jest.fn();

addCustomListener(element, callback);
const event = new Event('customEvent');
element.dispatchEvent(event);

expect(callback).toHaveBeenCalled();
});

This test ensures the custom event triggers the callback correctly.

70. Scenario: Testing complex form submission logic with async validation

Question: How do you test an async form validation and submission logic?

Answer:
Testing forms with async validation ensures users cannot submit invalid data.

For Example:

// validate.js
export async function validateUsername(username) {
return username === 'validUser' ? 'Valid' : 'Invalid';
}

// validate.test.js
test('validates username asynchronously', async () => {
const result = await validateUsername('validUser');
expect(result).toBe('Valid');
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
521

This test ensures the form behaves correctly based on async validation.

71. Scenario: Testing a feature flag implementation in React

Question: How do you test a component that conditionally renders content based on a
feature flag?

Answer:
Testing feature flags ensures that components behave correctly under different conditions
based on configuration values. This is particularly important in A/B testing scenarios.

For Example:

// FeatureComponent.js
import React from 'react';

function FeatureComponent({ isFeatureEnabled }) {


return (
<div>
{isFeatureEnabled ? <p>New Feature Enabled</p> : <p>Old Feature</p>}
</div>
);
}

export default FeatureComponent;

// FeatureComponent.test.js
import { render, screen } from '@testing-library/react';
import FeatureComponent from './FeatureComponent';

test('renders new feature when enabled', () => {


render(<FeatureComponent isFeatureEnabled={true} />);
expect(screen.getByText('New Feature Enabled')).toBeInTheDocument();
});

test('renders old feature when disabled', () => {


render(<FeatureComponent isFeatureEnabled={false} />);
expect(screen.getByText('Old Feature')).toBeInTheDocument();
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
522

This test checks the correct rendering of the component based on the feature flag.

72. Scenario: Testing a caching mechanism in JavaScript

Question: How do you test a function that implements caching to avoid redundant API calls?

Answer:
Testing caching ensures that the function retrieves data from the cache instead of making
unnecessary API calls. This can improve performance and reduce network load.

For Example:

// cache.js
const cache = new Map();

async function fetchWithCache(url) {


if (cache.has(url)) {
return cache.get(url);
}
const response = await fetch(url);
const data = await response.json();
cache.set(url, data);
return data;
}

module.exports = fetchWithCache;

// cache.test.js
global.fetch = jest.fn(() =>
Promise.resolve({ json: () => Promise.resolve({ data: 'Test data' }) })
);

const fetchWithCache = require('./cache');

test('fetches from API and caches the result', async () => {


const url = 'https://api.example.com/data';

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
523

// First call, should hit the API


const data1 = await fetchWithCache(url);
expect(data1).toEqual({ data: 'Test data' });
expect(fetch).toHaveBeenCalledTimes(1);

// Second call, should return cached data


const data2 = await fetchWithCache(url);
expect(data2).toEqual({ data: 'Test data' });
expect(fetch).toHaveBeenCalledTimes(1); // Should not hit the API again
});

This test ensures that the caching mechanism works as expected.

73. Scenario: Testing a custom hook that fetches data

Question: How do you test a custom hook that fetches data from an API using React Testing
Library?

Answer:
Testing custom hooks that perform API calls requires mocking the fetch requests and
verifying the behavior based on different loading and error states.

For Example:

// useFetch.js
import { useState, useEffect } from 'react';

export function useFetch(url) {


const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);

useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(url);
const result = await response.json();
setData(result);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
524

} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};

fetchData();
}, [url]);

return { data, error, loading };


}

// useFetch.test.js
import { renderHook } from '@testing-library/react-hooks';
import { useFetch } from './useFetch';

global.fetch = jest.fn();

test('fetches data successfully', async () => {


fetch.mockResolvedValueOnce({
json: jest.fn().mockResolvedValueOnce({ data: 'Test data' }),
});

const { result, waitForNextUpdate } = renderHook(() =>


useFetch('https://api.example.com'));

expect(result.current.loading).toBe(true);
await waitForNextUpdate();
expect(result.current.loading).toBe(false);
expect(result.current.data).toEqual({ data: 'Test data' });
expect(result.current.error).toBeNull();
});

test('handles errors correctly', async () => {


fetch.mockRejectedValueOnce(new Error('API Error'));

const { result, waitForNextUpdate } = renderHook(() =>


useFetch('https://api.example.com'));

expect(result.current.loading).toBe(true);
await waitForNextUpdate();
expect(result.current.loading).toBe(false);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
525

expect(result.current.data).toBeNull();
expect(result.current.error).toEqual(new Error('API Error'));
});

This test ensures the hook handles successful data fetching and errors appropriately.

74. Scenario: Testing form submission with validation in React

Question: How do you test a form submission with validation logic using React Testing
Library?

Answer:
Testing forms involves ensuring that the form validates inputs correctly before submission.
This helps confirm that users cannot submit invalid data.

For Example:

// SignUpForm.js
import React, { useState } from 'react';

function SignUpForm() {
const [email, setEmail] = useState('');
const [error, setError] = useState('');

const handleSubmit = (event) => {


event.preventDefault();
if (!/\S+@\S+\.\S+/.test(email)) {
setError('Email is invalid');
} else {
setError('');
// Proceed with form submission
}
};

return (
<form onSubmit={handleSubmit}>
<input
type="email"

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
526

value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
<button type="submit">Sign Up</button>
{error && <p>{error}</p>}
</form>
);
}

export default SignUpForm;

// SignUpForm.test.js
import { render, fireEvent, screen } from '@testing-library/react';
import SignUpForm from './SignUpForm';

test('shows error message on invalid email', () => {


render(<SignUpForm />);
fireEvent.change(screen.getByPlaceholderText('Email'), { target: { value:
'invalid-email' } });
fireEvent.click(screen.getByText('Sign Up'));
expect(screen.getByText('Email is invalid')).toBeInTheDocument();
});

This test ensures that the form displays an error message for invalid email input.

75. Scenario: Testing state management with Redux in complex


components

Question: How do you test a complex React component that interacts with Redux for state
management?

Answer:
When testing React components that interact with Redux, you can use a mock store to
isolate the component and verify its interaction with the store.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
527

// Counter.js
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';

function Counter() {
const count = useSelector((state) => state.count);
const dispatch = useDispatch();

return (
<div>
<p>Count: {count}</p>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>Increment</button>
</div>
);
}

export default Counter;

// Counter.test.js
import { render, screen, fireEvent } from '@testing-library/react';
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
import Counter from './Counter';

const mockStore = configureStore([]);

test('increments the counter', () => {


const store = mockStore({ count: 0 });
render(
<Provider store={store}>
<Counter />
</Provider>
);

fireEvent.click(screen.getByText('Increment'));
const actions = store.getActions();
expect(actions).toEqual([{ type: 'INCREMENT' }]);
});

This test checks if clicking the increment button dispatches the correct action.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
528

76. Scenario: Testing a loading spinner in a data-fetching component

Question: How do you test that a loading spinner displays during data fetching in a React
component?

Answer:
Testing components that show a loading spinner during data fetching ensures that users
receive feedback while waiting for data.

For Example:

// DataFetchingComponent.js
import React, { useEffect, useState } from 'react';

function DataFetchingComponent() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);

useEffect(() => {
const fetchData = async () => {
const response = await fetch('/api/data');
const result = await response.json();
setData(result);
setLoading(false);
};
fetchData();
}, []);

return (
<div>
{loading ? <p>Loading...</p> : <p>Data: {JSON.stringify(data)}</p>}
</div>
);
}

export default DataFetchingComponent;

// DataFetchingComponent.test.js
import { render, screen } from '@testing-library/react';
import DataFetchingComponent from './DataFetchingComponent';

global.fetch = jest.fn(() =>

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
529

Promise.resolve({
json: () => Promise.resolve({ data: 'Test data' }),
})
);

test('shows loading spinner during fetch', async () => {


render(<DataFetchingComponent />);
expect(screen.getByText('Loading...')).toBeInTheDocument();
await screen.findByText(/Data:/); // Wait for the data to be rendered
});

This test verifies that the loading message is displayed while data is being fetched.

77. Scenario: Testing a pagination component with data fetching

Question: How do you test a pagination component that fetches data for each page?

Answer:
Testing pagination ensures that the correct data is fetched for each page and displayed to
the user.

For Example:

// PaginationComponent.js
import React, { useEffect, useState } from 'react';

function PaginationComponent() {
const [data, setData] = useState([]);
const [currentPage, setCurrentPage] = useState(1);

useEffect(() => {
const fetchData = async () => {
const response = await fetch(`/api/data?page=${currentPage}`);
const result = await response.json();
setData(result);
};
fetchData();
}, [currentPage]);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
530

return (
<div>
{data.map((item) => (
<p key={item.id}>{item.name}</p>
))}
<button onClick={() => setCurrentPage((prev) => prev + 1)}>Next</button>
</div>
);
}

export default PaginationComponent;

// PaginationComponent.test.js
import { render, screen, fireEvent } from '@testing-library/react';
import PaginationComponent from './PaginationComponent';

global.fetch = jest.fn(() =>


Promise.resolve({
json: () => Promise.resolve([{ id: 1, name: 'Item 1' }]),
})
);

test('fetches data for the current page', async () => {


render(<PaginationComponent />);
expect(await screen.findByText('Item 1')).toBeInTheDocument();
fireEvent.click(screen.getByText('Next'));

fetch.mockResolvedValueOnce({
json: () => Promise.resolve([{ id: 2, name: 'Item 2' }]),
});

expect(await screen.findByText('Item 2')).toBeInTheDocument();


});

This test ensures that the correct data is fetched and displayed when navigating between
pages.

78. Scenario: Testing a modal component with user interactions

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
531

Question: How do you test a modal component that opens and closes based on user
interactions?

Answer:
Testing modal components involves verifying that they open and close correctly in response
to user actions.

For Example:

// Modal.js
import React, { useState } from 'react';

function Modal({ isOpen, onClose }) {


if (!isOpen) return null;

return (
<div>
<div>Modal Content</div>
<button onClick={onClose}>Close</button>
</div>
);
}

export default Modal;

// Modal.test.js
import { render, screen, fireEvent } from '@testing-library/react';
import Modal from './Modal';

test('opens and closes modal', () => {


const onClose = jest.fn();
render(<Modal isOpen={true} onClose={onClose} />);

expect(screen.getByText('Modal Content')).toBeInTheDocument();

fireEvent.click(screen.getByText('Close'));
expect(onClose).toHaveBeenCalled();
});

This test ensures that the modal opens when isOpen is true and calls the onClose function
when the close button is clicked.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
532

79. Scenario: Testing an infinite scroll component

Question: How do you test an infinite scroll component that loads more data as the user
scrolls down?

Answer:
Testing infinite scroll ensures that new data is fetched as the user scrolls to the bottom of the
component.

For Example:

// InfiniteScroll.js
import React, { useEffect, useState } from 'react';

function InfiniteScroll() {
const [items, setItems] = useState([]);

useEffect(() => {
const loadMoreItems = () => {
fetch('/api/items')
.then((res) => res.json())
.then((data) => setItems((prev) => [...prev, ...data]));
};

loadMoreItems();
window.addEventListener('scroll', loadMoreItems);

return () => {
window.removeEventListener('scroll', loadMoreItems);
};
}, []);

return (
<div>
{items.map((item) => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
533

export default InfiniteScroll;

// InfiniteScroll.test.js
import { render } from '@testing-library/react';
import InfiniteScroll from './InfiniteScroll';

global.fetch = jest.fn(() =>


Promise.resolve({
json: () => Promise.resolve([{ id: 1, name: 'Item 1' }]),
})
);

test('loads more items on scroll', async () => {


const { container } = render(<InfiniteScroll />);
expect(await screen.findByText('Item 1')).toBeInTheDocument();

window.scrollTo(0, document.body.scrollHeight);
fetch.mockResolvedValueOnce({
json: () => Promise.resolve([{ id: 2, name: 'Item 2' }]),
});

// Simulate another scroll event to load more items


window.dispatchEvent(new Event('scroll'));
expect(await screen.findByText('Item 2')).toBeInTheDocument();
});

This test ensures that additional items are fetched and rendered when the user scrolls.

80. Scenario: Testing localization in a React application

Question: How do you test that localized strings are rendered correctly in a React
component?

Answer:
Testing localization ensures that the correct strings are displayed based on the user’s
language preference. You can use a localization library and verify that the appropriate
translations are rendered.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
534

// LocalizationContext.js
import React, { createContext, useContext } from 'react';

const LocalizationContext = createContext();

export const LocalizationProvider = ({ children, language }) => {


const strings = {
en: { welcome: 'Welcome' },
es: { welcome: 'Bienvenido' },
};

return (
<LocalizationContext.Provider value={strings[language]}>
{children}
</LocalizationContext.Provider>
);
};

export const useLocalization = () => useContext(LocalizationContext);

// Welcome.js
import React from 'react';
import { useLocalization } from './LocalizationContext';

function Welcome() {
const { welcome } = useLocalization();
return <h1>{welcome}</h1>;
}

export default Welcome;

// Welcome.test.js
import { render } from '@testing-library/react';
import { LocalizationProvider } from './LocalizationContext';
import Welcome from './Welcome';

test('renders welcome message in English', () => {


render(
<LocalizationProvider language="en">
<Welcome />
</LocalizationProvider>
);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
535

expect(screen.getByText('Welcome')).toBeInTheDocument();
});

test('renders welcome message in Spanish', () => {


render(
<LocalizationProvider language="es">
<Welcome />
</LocalizationProvider>
);
expect(screen.getByText('Bienvenido')).toBeInTheDocument();
});

This test checks that the correct localized strings are rendered based on the provided
language context.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
536

Chapter 10: New Trends & Technologies in


JavaScript (2024)

THEORETICAL QUESTIONS
1. What is serverless architecture, and how does it benefit JavaScript
applications?

Answer:
Serverless architecture allows developers to build and run applications without managing
server infrastructure. Instead of provisioning servers, developers use cloud services like AWS
Lambda or Google Cloud Functions, which automatically scale and manage the execution
environment. This approach benefits JavaScript applications by simplifying deployment and
reducing operational costs.

For example, when using AWS Lambda, developers can upload their JavaScript code, define
triggers (like HTTP requests via API Gateway), and the service handles scaling based on
demand. This means that resources are only used when needed, leading to cost savings.

For Example:
Here's a simple AWS Lambda function written in JavaScript:

exports.handler = async (event) => {


const response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
return response;
};

In this example, the function returns a JSON response when triggered.

2. Explain how AWS Lambda works with JavaScript.

Answer:
AWS Lambda is a serverless compute service that lets developers run code without
provisioning or managing servers. It supports JavaScript through Node.js, allowing

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
537

developers to write Lambda functions in JavaScript. When an event occurs (like an API call or
a scheduled task), AWS Lambda runs the specified code in response.

Lambda scales automatically with the number of incoming requests, so there is no need to
worry about infrastructure management. This architecture is particularly useful for
microservices or backend functionalities.

For Example:
To set up a simple AWS Lambda function in JavaScript:

exports.handler = async (event) => {


console.log('Event received:', event);
return {
statusCode: 200,
body: JSON.stringify({ message: 'Success' }),
};
};

In this example, the function logs the incoming event and returns a success message.

3. What are Cloud Functions, and how do they differ from AWS Lambda?

Answer:
Cloud Functions are Google Cloud's serverless execution environment that allows developers
to run code in response to events. They differ from AWS Lambda in terms of service
integration, environment setup, and the specific cloud ecosystem they belong to. While both
services provide similar functionality in terms of scaling and cost efficiency, they are tailored
to their respective cloud platforms.

Cloud Functions support a variety of languages, including JavaScript (Node.js), and integrate
seamlessly with other Google services like Firebase, Pub/Sub, and Google Cloud Storage. This
makes them ideal for building applications within the Google Cloud ecosystem.

For Example:
Here’s a simple Google Cloud Function example in JavaScript:

exports.helloWorld = (req, res) => {


res.send('Hello, World!');
};

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
538

In this example, the function responds to an HTTP request with a greeting.

4. Describe edge computing and its significance in JavaScript


development.

Answer:
Edge computing refers to the practice of processing data closer to the source of generation
rather than relying on a centralized data center. This is particularly significant in JavaScript
development as it reduces latency, improves performance, and enhances user experience by
allowing real-time data processing.

With the rise of IoT devices and mobile applications, JavaScript can run on edge servers, such
as those provided by Cloudflare Workers or Deno Deploy, enabling developers to build
applications that respond quickly to user actions.

For Example:
In a Cloudflare Worker, you can create an API that runs at the edge:

addEventListener('fetch', event => {


event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {


return new Response('Hello from the Edge!', { status: 200 });
}

This example shows a simple Worker that responds to fetch events with a message.

5. What is Deno, and how does it differ from Node.js?

Answer:
Deno is a modern runtime for JavaScript and TypeScript that is built on Rust and V8. It differs
from Node.js in several key aspects, including security, module management, and built-in
TypeScript support.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
539

Deno executes code in a secure sandbox by default, requiring explicit permissions to access
files, network, and environment variables. This enhances security compared to Node.js, where
the code has full access to the operating system by default.

For Example:
Here’s how you would import a module in Deno:

import { serve } from "https://deno.land/std/http/server.ts";

const server = serve({ port: 8000 });


console.log("HTTP server is running.");

for await (const req of server) {


req.respond({ body: "Hello, Deno!" });
}

In this example, Deno serves an HTTP response while demonstrating its module system.

6. How can JavaScript be used in IoT development?

Answer:
JavaScript can be effectively used in IoT development through frameworks and libraries that
enable communication with IoT devices. Platforms like Node-RED or Johnny-Five provide
tools for building IoT applications using JavaScript.

These tools allow developers to control hardware, gather data from sensors, and
communicate with cloud services, all using JavaScript, making it accessible for web
developers to enter the IoT space.

For Example:
Here’s a basic example using Johnny-Five to control an LED:

const { Board, Led } = require("johnny-five");


const board = new Board();

board.on("ready", () => {
const led = new Led(13);
led.blink(500); // Blink LED every 500ms
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
540

In this example, the LED on pin 13 blinks at half-second intervals.

7. What is TensorFlow.js, and how can it be applied in JavaScript


applications?

Answer:
TensorFlow.js is a library that enables developers to define, train, and run machine learning
models in JavaScript. It allows for both browser-based and Node.js environments, making it
versatile for various applications.

Using TensorFlow.js, developers can leverage pre-trained models or build custom models to
perform tasks like image recognition, natural language processing, and more. This opens up
possibilities for adding AI capabilities directly into web applications without needing a
backend.

For Example:
Here’s a simple example of loading a pre-trained model to classify an image:

const mobilenet = require('@tensorflow-models/mobilenet');


const tf = require('@tensorflow/tfjs');

async function classifyImage(image) {


const model = await mobilenet.load();
const predictions = await model.classify(image);
console.log(predictions);
}

In this example, an image is classified using the MobileNet model, demonstrating how to
implement AI in a JavaScript application.

8. Discuss Brain.js and its use cases in JavaScript.

Answer:
Brain.js is a JavaScript library for neural networks that allows developers to create and train
networks in a straightforward manner. It provides an easy interface for defining different
types of networks, including feedforward and recurrent networks, and can be run in both
Node.js and browser environments.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
541

Brain.js is ideal for projects requiring basic machine learning functionalities, such as pattern
recognition, prediction, and classification tasks. It is particularly useful for developers who
may not have a strong background in machine learning but want to incorporate AI features
into their applications.

For Example:
Here’s a simple neural network example using Brain.js:

const brain = require('brain.js');


const net = new brain.NeuralNetwork();

net.train([{ input: [0, 0], output: [0] }, { input: [0, 1], output: [1] }]);

const output = net.run([1, 0]); // Output for input [1, 0]


console.log(output); // Should be close to 1

In this example, the neural network learns the XOR function, demonstrating how Brain.js can
be used for simple AI tasks.

9. What is WebAssembly, and how does it relate to JavaScript?

Answer:
WebAssembly (Wasm) is a binary instruction format that allows code written in languages
like C, C++, and Rust to run in the browser at near-native speed. It complements JavaScript by
enabling performance-intensive tasks to be executed more efficiently than JavaScript alone.

Wasm is designed to be a compilation target for high-level languages, allowing developers to


utilize existing codebases while maintaining the interactivity and ease of use provided by
JavaScript. This integration opens up new possibilities for web applications, especially those
requiring complex calculations or processing.

For Example:
Here’s a simple way to compile and use WebAssembly in a JavaScript application:

async function loadWasm() {


const response = await fetch('module.wasm');
const buffer = await response.arrayBuffer();
const module = await WebAssembly.instantiate(buffer);
console.log(module.instance.exports.exportedFunction());

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
542

}
loadWasm();

In this example, a WebAssembly module is loaded and its exported function is called from
JavaScript.

10. What are Progressive Web Apps (PWAs), and what role does JavaScript
play in their development?

Answer:
Progressive Web Apps (PWAs) are web applications that utilize modern web capabilities to
deliver an app-like experience to users. They are designed to work on any platform that uses
a standards-compliant browser and offer features like offline access, push notifications, and
installation on the user’s device.

JavaScript plays a crucial role in PWA development, as it is used to implement service


workers, manage caching, and handle dynamic content updates. The use of JavaScript allows
developers to create responsive and engaging user interfaces while ensuring that the app
functions well in various network conditions.

For Example:
Here’s how you might register a service worker in a PWA:

if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js')
.then(registration => {
console.log('Service Worker registered with scope:',
registration.scope);
});
});
}

In this example, a service worker is registered, allowing the PWA to cache resources and
provide offline functionality.

11. How do modern frontend tools like Vite improve JavaScript


development?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
543

Answer:
Modern frontend tools like Vite significantly improve JavaScript development by providing
faster builds and enhanced development experiences. Vite leverages native ES modules and
serves files over native ESM in the browser, resulting in faster refresh times during
development. This approach eliminates the need for complex bundling during the
development phase, which can slow down the feedback loop.

Additionally, Vite includes features like hot module replacement (HMR), which allows
developers to see changes in real-time without losing application state. These improvements
streamline the development process, making it more efficient and enjoyable.

For Example:
To create a new project with Vite, you can use the following command:

npm create vite@latest my-vite-app

This command initializes a new Vite project, providing a fast and modern setup for building
JavaScript applications.

12. What is TurboRepo, and how does it enhance JavaScript project


management?

Answer:
TurboRepo is a high-performance build system for JavaScript and TypeScript monorepos. It
optimizes the build process by leveraging caching and parallel execution, enabling
developers to work with large codebases efficiently.

By using TurboRepo, teams can significantly reduce build times, enhance productivity, and
streamline workflows across multiple packages within a monorepo. It also simplifies the
management of dependencies and versioning, making it easier to maintain complex
projects.

For Example:
To add TurboRepo to a project, you can initialize it with:

npx turbo init

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
544

This command sets up TurboRepo, allowing you to configure builds and manage tasks across
your monorepo efficiently.

13. How does Next.js facilitate server-side rendering for JavaScript


applications?

Answer:
Next.js is a popular React framework that simplifies server-side rendering (SSR) for JavaScript
applications. It allows developers to build applications that can render pages on the server,
improving performance and SEO by delivering fully rendered HTML to the client.

With Next.js, developers can create pages that load data asynchronously during the server-
side rendering process, resulting in faster initial load times and a better user experience. The
framework also supports static site generation, allowing developers to pre-render pages at
build time.

For Example:
Here’s how you can implement SSR in a Next.js page:

export async function getServerSideProps() {


const res = await fetch('https://api.example.com/data');
const data = await res.json();

return { props: { data } };


}

export default function Page({ data }) {


return <div>{data.title}</div>;
}

In this example, the getServerSideProps function fetches data at request time, ensuring
that the page is rendered with up-to-date content.

14. Explain the concept of micro-frontends and their advantages in


JavaScript applications.

Answer:
Micro-frontends is an architectural style where a web application is built as a composition of
smaller, independent frontend applications. Each micro-frontend can be developed,

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
545

deployed, and scaled independently, allowing teams to work on different parts of the
application without impacting each other.

This approach brings several advantages, including improved scalability, easier maintenance,
and the ability to use different technologies for different parts of the application. It also
facilitates collaboration across teams, as each team can own and manage their respective
micro-frontend.

For Example:
In a micro-frontend architecture, you might have separate teams handling different parts of
an application, like the user profile, dashboard, and settings. Each team can choose their
technology stack and deploy independently, while a shell application orchestrates the overall
user experience.

15. What are the best practices for building Progressive Web Apps with
JavaScript?

Answer:
Building Progressive Web Apps (PWAs) with JavaScript involves following several best
practices to ensure optimal performance and user experience. Some key practices include:

1. Service Worker Implementation: Use service workers for caching assets and enabling
offline functionality.
2. Responsive Design: Ensure the app is responsive and works on various devices and
screen sizes.
3. Performance Optimization: Optimize loading times by using techniques like lazy
loading and code splitting.
4. HTTPS: Serve the application over HTTPS to ensure security.
5. Manifest File: Include a web app manifest to control how the app appears when
installed on a device.

For Example:
A simple service worker implementation might look like this:

self.addEventListener('install', (event) => {


event.waitUntil(
caches.open('v1').then((cache) => {
return cache.addAll(['/index.', '/styles.css']);
})
);
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
546

This example demonstrates how to cache essential files during the service worker installation
process.

16. How does the concept of declarative UI apply to modern JavaScript


frameworks?

Answer:
Declarative UI is a programming paradigm where developers describe what the UI should
look like based on the application's state rather than how to change the UI. Modern
JavaScript frameworks like React and Vue.js embrace this approach, allowing developers to
build user interfaces that automatically update when the underlying data changes.

In declarative UI, the framework takes care of updating the DOM, resulting in more
predictable and easier-to-maintain code. This leads to enhanced development speed and
reduced bugs, as developers can focus on defining the desired state without worrying about
manual DOM manipulation.

For Example:
In React, a declarative approach might look like this:

function App() {
const [count, setCount] = useState(0);

return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}

Here, the UI automatically reflects the current count value without requiring direct DOM
manipulation.

17. What role do state management libraries play in modern JavaScript


applications?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
547

Answer:
State management libraries, such as Redux and MobX, play a crucial role in managing the
state of modern JavaScript applications. They provide a centralized store to hold the
application’s state, allowing components to access and update state in a predictable manner.

Using state management libraries helps in maintaining consistency across the application,
especially in large and complex applications where multiple components depend on shared
state. These libraries also facilitate debugging and testing, as developers can track state
changes over time.

For Example:
Here’s a basic example of using Redux for state management:

import { createStore } from 'redux';

const initialState = { count: 0 };

function reducer(state = initialState, action) {


switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
default:
return state;
}
}

const store = createStore(reducer);


store.dispatch({ type: 'INCREMENT' });
console.log(store.getState()); // { count: 1 }

In this example, Redux is used to manage a simple count state, showcasing its ability to
handle state changes.

18. How do you implement routing in a modern JavaScript application?

Answer:
Routing in modern JavaScript applications is typically handled by libraries such as React
Router for React applications or Vue Router for Vue.js applications. These libraries enable
developers to define navigation paths within the application, allowing users to move
between different views or components based on the URL.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
548

Implementing routing involves creating routes that map URLs to specific components,
enabling a single-page application (SPA) experience. This approach improves performance
by loading content dynamically without refreshing the entire page.

For Example:
Here’s how you would set up basic routing in a React application using React Router:

import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';


import Home from './Home';
import About from './About';

function App() {
return (
<Router>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/about" component={About} />
</Switch>
</Router>
);
}

In this example, the application routes to the Home component for the root path and the
About component for the /about path.

19. What is the significance of code splitting in JavaScript applications?

Answer:
Code splitting is a technique that allows developers to break down their JavaScript
applications into smaller, manageable chunks that can be loaded on demand. This is
particularly significant in large applications where loading the entire codebase at once can
lead to long loading times and a poor user experience.

By implementing code splitting, developers can improve performance, as only the necessary
code for the current view is loaded initially. This reduces the initial load time and improves
responsiveness, especially in single-page applications (SPAs).

For Example:
In a React application, you can use dynamic imports for code splitting:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
549

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function App() {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</React.Suspense>
);
}

In this example, the OtherComponent is loaded only when needed, improving the initial
loading performance of the application.

20. Explain the role of build tools like Webpack in JavaScript development.

Answer:
Webpack is a powerful module bundler that plays a crucial role in modern JavaScript
development. It processes and bundles JavaScript files, as well as other assets like CSS,
images, and HTML, into a single output file or multiple files optimized for production.

Using Webpack allows developers to take advantage of features like code splitting, tree
shaking, and asset management, which improve application performance and
maintainability. Webpack’s extensive plugin ecosystem also enables customization and
enhancement of the build process, making it a valuable tool in the JavaScript development
workflow.

For Example:
A basic Webpack configuration might look like this:

const path = require('path');

module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
550

test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
],
},
};

In this example, Webpack is configured to bundle JavaScript files from the src directory into
a single bundle.js file in the dist directory, using Babel for transpilation.

21. Explain the concept of closures in JavaScript and provide an example of


how they can be used.

Answer:
Closures are a fundamental concept in JavaScript that occurs when a function retains access
to its lexical scope, even when the function is executed outside that scope. This is possible
because functions in JavaScript create their own scope and can "close over" variables that
were defined in that scope, allowing for data encapsulation and privacy.

Closures are useful for creating private variables, implementing factory functions, and
managing state in asynchronous operations.

For Example:
Here’s an example demonstrating closures:

function makeCounter() {
let count = 0; // Private variable
return function() {
count++; // Accessing the private variable
return count;
};
}

const counter = makeCounter();

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
551

console.log(counter()); // Output: 1
console.log(counter()); // Output: 2

In this example, the inner function retains access to the count variable, demonstrating how
closures allow for private state.

22. What are the differences between synchronous and asynchronous


programming in JavaScript?

Answer:
Synchronous programming executes code sequentially, meaning each operation must
complete before the next one begins. This can lead to blocking, where the execution of the
entire program halts while waiting for time-consuming tasks, such as network requests or
file reading.

Asynchronous programming, on the other hand, allows operations to run concurrently. This
is particularly useful for tasks that take time to complete, as it enables the program to
continue executing other code while waiting for the asynchronous task to finish. JavaScript
handles asynchronous programming through callbacks, promises, and async/await syntax,
allowing for more responsive applications.

For Example:
Here's a comparison of synchronous and asynchronous code:

// Synchronous
console.log('Start');
console.log('Synchronous operation');
console.log('End');

// Asynchronous
console.log('Start');
setTimeout(() => {
console.log('Asynchronous operation');
}, 1000);
console.log('End');

In the asynchronous example, the "End" message is logged before the asynchronous
operation completes, demonstrating non-blocking behavior.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
552

23. How do you optimize performance in JavaScript applications?

Answer:
Optimizing performance in JavaScript applications involves various techniques that aim to
enhance speed and efficiency. Key strategies include:

1. Minimizing DOM Manipulation: Direct manipulation of the DOM is expensive. Batch


updates and use techniques like virtual DOM where applicable.
2. Debouncing and Throttling: Use these techniques to limit the rate at which functions
are executed, especially for events like scrolling or resizing.
3. Code Splitting: Implement code splitting to load only necessary code when required,
reducing initial load times.
4. Using Web Workers: Offload heavy computations to Web Workers to prevent
blocking the main thread.
5. Optimizing Network Requests: Use caching strategies and minimize the number of
network requests to speed up loading times.

For Example:
Here’s an example of debouncing a function:

function debounce(func, delay) {


let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), delay);
};
}

const handleResize = debounce(() => {


console.log('Window resized');
}, 200);

window.addEventListener('resize', handleResize);

In this example, the handleResize function is debounced, ensuring it only executes after the
resize event has stopped for 200 milliseconds, improving performance.

24. What is the event loop in JavaScript, and how does it work?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
553

Answer:
The event loop is a fundamental mechanism in JavaScript that allows for asynchronous
programming. It continuously checks the call stack for pending function calls and the
message queue for any messages (events) that need to be processed. When the call stack is
empty, the event loop dequeues messages from the message queue and executes their
associated callback functions.

This mechanism enables JavaScript to handle asynchronous operations without blocking the
main thread, allowing for smooth user interactions and responsiveness.

For Example:
Here’s a simple demonstration of the event loop:

console.log('Start');

setTimeout(() => {
console.log('Timeout callback');
}, 0);

console.log('End');

This shows that the synchronous code runs first, and only after the call stack is empty does
the timeout callback execute, illustrating how the event loop processes asynchronous tasks.

25. What are Promises and how do they improve error handling in
JavaScript?

Answer:
Promises are objects that represent the eventual completion (or failure) of an asynchronous
operation and its resulting value. They provide a more manageable way to handle
asynchronous operations compared to callbacks, particularly regarding error handling.

With promises, you can chain .then() methods for success and use .catch() for errors,
allowing for cleaner and more readable code. Promises help avoid "callback hell" and make it
easier to manage complex asynchronous flows.

For Example:
Here’s how you can use promises:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
554

const fetchData = () => {


return new Promise((resolve, reject) => {
// Simulating an asynchronous operation
setTimeout(() => {
const success = true; // Simulated result
if (success) {
resolve('Data fetched successfully');
} else {
reject('Error fetching data');
}
}, 1000);
});
};

fetchData()
.then(data => console.log(data))
.catch(error => console.error(error));

In this example, the promise resolves successfully, and the resulting message is logged,
demonstrating how promises improve error handling and code clarity.

26. What is the difference between null and undefined in JavaScript?

Answer:
In JavaScript, null and undefined are both primitive values but serve different purposes.
undefined is the default value assigned to uninitialized variables or when a function does not
return a value. It signifies that a variable has been declared but has not yet been assigned a
value.

On the other hand, null is an intentional assignment value that represents the absence of
any object value. It indicates that a variable should be empty or has been deliberately set to
have no value.

For Example:
Here’s a demonstration of their differences:

let a; // Declared but not initialized


let b = null; // Explicitly set to null

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
555

console.log(a); // Output: undefined


console.log(b); // Output: null

console.log(typeof a); // Output: "undefined"


console.log(typeof b); // Output: "object"

In this example, a is undefined, while b is null, showcasing their distinct meanings and uses.

27. How do you implement inheritance in JavaScript, and what are the
different ways to achieve it?

Answer:
Inheritance in JavaScript can be implemented using several methods, including:

1. Prototypal Inheritance: This is the most common method in JavaScript, where


objects inherit properties and methods from other objects through the prototype
chain. You can set the prototype of one object to another using Object.create().
2. Constructor Functions: Functions can be used as constructors with the new keyword
to create objects that inherit from their prototype.
3. ES6 Classes: Introduced in ECMAScript 2015, classes provide a clearer syntax for
creating objects and managing inheritance, allowing for the use of extends and
super.

For Example:
Here’s an example using ES6 classes:

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

speak() {
console.log(`${this.name} makes a noise.`);
}
}

class Dog extends Animal {


speak() {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
556

console.log(`${this.name} barks.`);
}
}

const dog = new Dog('Rex');


dog.speak(); // Output: Rex barks.

In this example, the Dog class inherits from the Animal class, overriding the speak method to
provide specific behavior.

28. What are template literals in JavaScript, and how do they differ from
traditional string concatenation?

Answer:
Template literals are a feature introduced in ECMAScript 2015 (ES6) that allow for easier and
more readable string manipulation. They use backticks (`) instead of quotes, enabling multi-
line strings and embedded expressions through placeholders indicated by the
${expression} syntax.

Template literals provide several advantages over traditional string concatenation:

1. Multi-line Strings: They allow for easy creation of strings that span multiple lines
without the need for escape characters.
2. String Interpolation: You can easily embed variables and expressions directly into the
string.

For Example:
Here’s a comparison of traditional string concatenation and template literals:

const name = 'John';


const age = 30;

// Traditional concatenation
const greeting1 = 'Hello, my name is ' + name + ' and I am ' + age + ' years old.';

// Using template literals


const greeting2 = `Hello, my name is ${name} and I am ${age} years old.`;

console.log(greeting1); // Output: Hello, my name is John and I am 30 years old.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
557

console.log(greeting2); // Output: Hello, my name is John and I am 30 years old.

In this example, template literals offer a more concise and readable way to construct strings
compared to traditional concatenation.

29. How does the spread operator work in JavaScript, and what are its
common use cases?

Answer:
The spread operator, represented by three dots (...), is a syntax introduced in ES6 that
allows iterable objects (like arrays and strings) to be expanded into individual elements. This
operator can be used in various contexts, including function calls, array literals, and object
literals.

Common use cases for the spread operator include:

1. Copying Arrays: Creating shallow copies of arrays to avoid mutations.


2. Merging Arrays: Combining multiple arrays into one.
3. Function Arguments: Spreading elements as individual arguments in function calls.
4. Object Merging: Merging properties from multiple objects.

For Example:
Here’s how the spread operator can be used:

const arr1 = [1, 2, 3];


const arr2 = [4, 5, 6];

// Copying and merging arrays


const mergedArray = [...arr1, ...arr2]; // Output: [1, 2, 3, 4, 5, 6]

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

// Merging objects
const mergedObject = { ...obj1, ...obj2 }; // Output: { a: 1, b: 3, c: 4 }

console.log(mergedArray);
console.log(mergedObject);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
558

In this example, the spread operator is used to merge arrays and objects, showcasing its
versatility and convenience.

30. What are JavaScript modules, and how do they differ from scripts?

Answer:
JavaScript modules are reusable pieces of code that can be exported from one file and
imported into another. They provide a way to encapsulate functionality and manage
dependencies between different parts of an application, promoting better organization and
maintainability.

The main differences between modules and traditional scripts include:

1. Encapsulation: Modules allow for better encapsulation of variables and functions,


avoiding polluting the global scope.
2. Import/Export: Modules use export and import syntax to share code, while scripts
simply execute in the global context.
3. Static Structure: Modules have a static structure, which allows for better optimization
by the JavaScript engine compared to dynamic scripts.

For Example:
Here’s how to define and use a module:

// math.js
export function add(x, y) {
return x + y;
}

// main.js
import { add } from './math.js';

console.log(add(2, 3)); // Output: 5

In this example, the add function is defined in a module and imported into another file,
demonstrating how modules improve code organization and reusability.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
559

31. How do you implement error handling in asynchronous JavaScript, and


what are the best practices?

Answer:
Error handling in asynchronous JavaScript is crucial for maintaining application stability and
providing a good user experience. There are several best practices for handling errors
effectively:

1. Use try/catch with async/await: When using async/await, wrap your code in a
try/catch block to handle rejected promises easily.
2. Use .catch() for Promises: When working with promises, always attach a .catch()
method to handle any potential errors that may arise during the asynchronous
operation.
3. Graceful Degradation: Ensure that your application can handle errors gracefully,
providing feedback to users without crashing.
4. Logging: Implement logging mechanisms to capture and record errors for
debugging purposes.

For Example:
Here’s how to handle errors with async/await and promises:

async function fetchData(url) {


try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Fetch error:', error);
// Handle error gracefully, e.g., show a message to the user
}
}

fetchData('https://api.example.com/data')
.then(data => console.log(data))
.catch(error => console.error('Promise error:', error));

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
560

In this example, both async/await and promise error handling are demonstrated, showcasing
how to manage errors effectively in asynchronous operations.

32. What are the new features introduced in ES2020 (ES11), and how do
they enhance JavaScript development?

Answer:
ES2020 introduced several new features that enhance JavaScript development, making code
more concise, efficient, and easier to manage. Some notable features include:

1. Optional Chaining (?.): This feature allows safe access to deeply nested properties
without having to check for the existence of each level, preventing runtime errors.
2. Nullish Coalescing Operator (??): This operator provides a way to set default values
only when the left-hand side is null or undefined, improving how defaults are
assigned.
3. BigInt: A new primitive type for representing integers larger than 2^53 - 1, which
allows for precise arithmetic on very large numbers.
4. Dynamic import(): This enables loading modules dynamically, making it possible to
load code on demand.

For Example:
Here’s how to use optional chaining and nullish coalescing:

const user = {
name: 'Alice',
address: {
street: '123 Main St',
city: 'Wonderland',
},
};

// Using optional chaining


const city = user.address?.city ?? 'Unknown';
console.log(city); // Output: Wonderland

const age = user.age ?? 18; // Default to 18 if age is null or undefined


console.log(age); // Output: 18

In this example, optional chaining prevents errors when accessing nested properties, and the
nullish coalescing operator provides a default value only when necessary.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
561

33. Discuss the concept of functional programming in JavaScript and its


core principles.

Answer:
Functional programming is a programming paradigm that treats computation as the
evaluation of mathematical functions and avoids changing state and mutable data. In
JavaScript, functional programming is widely used and encourages the following core
principles:

1. First-Class Functions: Functions are treated as first-class citizens, allowing them to be


passed as arguments, returned from other functions, and assigned to variables.
2. Pure Functions: Functions that do not have side effects and return the same output
for the same input are considered pure. They make the code predictable and easier to
test.
3. Higher-Order Functions: Functions that take other functions as arguments or return
them as results facilitate composition and abstraction.
4. Immutability: Promoting immutability by avoiding changes to existing data
structures encourages safer and more reliable code.

For Example:
Here’s an example illustrating functional programming concepts:

// Higher-order function
const map = (arr, fn) => arr.map(fn);

// Pure function
const square = (x) => x * x;

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


const squaredNumbers = map(numbers, square);

console.log(squaredNumbers); // Output: [1, 4, 9, 16]

In this example, map is a higher-order function that takes another function (square) as an
argument, demonstrating functional programming principles in action.

34. Explain the concept of immutability and how it can be achieved in


JavaScript.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
562

Answer:
Immutability refers to the idea that data cannot be changed after it has been created. In
JavaScript, immutability can lead to more predictable and maintainable code, especially in
applications where state management is crucial (e.g., in React applications).

Immutability can be achieved through various techniques:

1. Object.freeze: This method prevents modification of an object’s properties, making it


immutable.
2. Using Spread Operator: The spread operator can create shallow copies of arrays or
objects, preventing changes to the original data.
3. Libraries: Libraries like Immutable.js provide data structures that are inherently
immutable.

For Example:
Here’s how to achieve immutability using the spread operator:

const originalArray = [1, 2, 3];


const newArray = [...originalArray, 4]; // Create a new array

console.log(originalArray); // Output: [1, 2, 3]


console.log(newArray); // Output: [1, 2, 3, 4]

const originalObject = { a: 1, b: 2 };
const newObject = { ...originalObject, c: 3 }; // Create a new object

console.log(originalObject); // Output: { a: 1, b: 2 }
console.log(newObject); // Output: { a: 1, b: 2, c: 3 }

In this example, the original array and object remain unchanged, demonstrating how to
implement immutability in JavaScript.

35. How do you create and use decorators in JavaScript, and what are their
common use cases?

Answer:
Decorators in JavaScript are a special kind of declaration that can be attached to a class or
method to modify its behavior. While decorators are not yet part of the official JavaScript
specification, they can be implemented using a proposal in environments that support them
(such as TypeScript or Babel).

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
563

Common use cases for decorators include:

1. Logging: Adding logging capabilities to class methods for tracking calls and
performance.
2. Validation: Implementing input validation for method parameters.
3. Access Control: Restricting access to certain methods based on user roles.

For Example:
Here’s a simple example of a method decorator:

function log(target, key, descriptor) {


const originalMethod = descriptor.value;

descriptor.value = function(...args) {
console.log(`Calling ${key} with arguments: ${args}`);
return originalMethod.apply(this, args);
};

return descriptor;
}

class Calculator {
@log
add(a, b) {
return a + b;
}
}

const calc = new Calculator();


calc.add(2, 3); // Output: Calling add with arguments: 2,3

In this example, the log decorator enhances the add method to log its calls, illustrating how
decorators can modify method behavior.

36. What are the differences between shallow copy and deep copy in
JavaScript?

Answer:
In JavaScript, copying an object can be done in two ways: shallow copy and deep copy.
Understanding these concepts is essential for managing data structures effectively.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
564

1. Shallow Copy: A shallow copy creates a new object that has the same properties as
the original object, but it only copies the references of nested objects. This means that
changes to nested objects will affect both the original and the copied object.
2. Deep Copy: A deep copy creates a new object and recursively copies all nested
objects, ensuring that the original and copied objects are completely independent.
Changes to nested objects in the copied object do not affect the original object.

For Example:
Here’s how to create shallow and deep copies:

const original = { a: 1, b: { c: 2 } };

// Shallow copy using Object.assign


const shallowCopy = Object.assign({}, original);
shallowCopy.b.c = 3; // Affects the original object

console.log(original.b.c); // Output: 3

// Deep copy using JSON methods


const deepCopy = JSON.parse(JSON.stringify(original));
deepCopy.b.c = 4; // Does not affect the original object

console.log(original.b.c); // Output: 3

In this example, the shallow copy modifies the original object due to shared references, while
the deep copy remains independent.

37. What is a service worker, and how does it enhance web application
performance?

Answer:
A service worker is a script that runs in the background of a web application, separate from
the main browser thread. It acts as a proxy between the web application and the network,
enabling features like caching, background sync, and push notifications.

Service workers enhance web application performance by:

1. Offline Support: They allow applications to work offline by caching assets and
resources.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
565

2. Caching Strategies: Service workers can implement caching strategies to improve


load times and reduce network requests.
3. Background Sync: They enable applications to synchronize data in the background
when the network is available.

For Example:
Here’s a basic implementation of a service worker:

self.addEventListener('install', (event) => {


event.waitUntil(
caches.open('my-cache').then((cache) => {
return cache.addAll([
'/',
'/index.',
'/styles.css',
'/script.js',
]);
})
);
});

self.addEventListener('fetch', (event) => {


event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});

In this example, the service worker caches specified files during installation and serves
cached responses when possible, improving the application’s performance and offline
capabilities.

38. Explain how to manage state in React applications and the role of
hooks like useState and useReducer.

Answer:
Managing state in React applications is essential for creating dynamic and interactive user
interfaces. React provides several tools for state management, including hooks like useState
and useReducer.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
566

1. useState: This hook allows functional components to manage local state. It returns a
state variable and a function to update that state, enabling components to respond to
user interactions and other changes.
2. useReducer: This hook is useful for managing more complex state logic. It allows you
to define a reducer function that specifies how the state changes in response to
actions. This approach is similar to Redux and is beneficial for applications with
intricate state management needs.

For Example:
Here’s how to use useState and useReducer:

// Using useState
import React, { useState } from 'react';

function Counter() {
const [count, setCount] = useState(0);

return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}

// Using useReducer
import React, { useReducer } from 'react';

const initialState = { count: 0 };


function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}

function CounterWithReducer() {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
567

const [state, dispatch] = useReducer(reducer, initialState);

return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment'
})}>Increment</button>
<button onClick={() => dispatch({ type: 'decrement'
})}>Decrement</button>
</div>
);
}

In these examples, useState is used for simple state management, while useReducer
provides a structured way to handle more complex state transitions.

39. What are web components, and how do they enhance modularity in
web development?

Answer:
Web components are a set of web platform APIs that allow developers to create reusable
custom elements with encapsulated functionality and styling. They consist of three main
technologies:

1. Custom Elements: Allow the definition of new HTML elements.


2. Shadow DOM: Provides encapsulated DOM for a custom element, preventing styles
and scripts from leaking in or out.
3. HTML Templates: Define markup that is not rendered immediately but can be
instantiated later.

Web components enhance modularity in web development by promoting the creation of


self-contained, reusable components that can be used across different applications or
frameworks, reducing code duplication and improving maintainability.

For Example:
Here’s a simple web component definition:

class MyButton extends HTMLElement {


constructor() {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
568

super();
const shadow = this.attachShadow({ mode: 'open' });
const button = document.createElement('button');
button.textContent = 'Click me';
button.addEventListener('click', () => {
alert('Button clicked!');
});
shadow.appendChild(button);
}
}

customElements.define('my-button', MyButton);

In this example, a custom button element is defined using the Web Components API,
allowing it to be used anywhere in the HTML.

40. How do you handle performance optimization for large-scale


JavaScript applications?

Answer:
Handling performance optimization for large-scale JavaScript applications involves several
strategies to ensure responsiveness and efficient resource management:

1. Code Splitting: Use dynamic imports and tools like Webpack to split the codebase
into smaller chunks, loading only what is necessary.
2. Lazy Loading: Implement lazy loading for images and other assets, ensuring they are
only loaded when they enter the viewport.
3. Memoization: Use memoization techniques to cache expensive function calls and
avoid redundant computations.
4. Debouncing and Throttling: Limit the rate of function executions for events like
scrolling or resizing to reduce workload during high-frequency events.
5. Reduce Repaints and Reflows: Minimize direct DOM manipulations and batch
updates to prevent excessive layout recalculations.

For Example:
Here’s an example of memoization using a simple function:

const memoize = (fn) => {


const cache = {};

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
569

return function(...args) {
const key = JSON.stringify(args);
if (!cache[key]) {
cache[key] = fn(...args);
}
return cache[key];
};
};

const factorial = memoize((n) => {


if (n === 0) return 1;
return n * factorial(n - 1);
});

console.log(factorial(5)); // Output: 120


console.log(factorial(5)); // Cached result: Output: 120

In this example, the factorial function is memoized, significantly improving performance


for repeated calls with the same argument.

SCENARIO QUESTIONS
Scenario 41: Implementing Serverless Functions

You are tasked with building a simple serverless function to handle user sign-ups for a web
application using AWS Lambda. The function should receive user data, save it to a database,
and return a success message.

Question: How would you structure the AWS Lambda function to handle user sign-ups?
What considerations would you have for error handling?

Answer:
To implement a serverless function for user sign-ups using AWS Lambda, I would first set up
the Lambda function to handle incoming events, likely from an API Gateway. The function
should validate the incoming user data, save it to a database (like DynamoDB), and return a
response.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
570

Error handling is crucial in this setup. I would use a try-catch block to catch any errors during
the database operation, ensuring the function returns a meaningful error message without
exposing sensitive information.

For Example:
Here’s how the AWS Lambda function could be structured:

const AWS = require('aws-sdk');


const dynamoDB = new AWS.DynamoDB.DocumentClient();

exports.handler = async (event) => {


const { username, email } = JSON.parse(event.body);
const params = {
TableName: 'Users',
Item: { username, email },
};

try {
await dynamoDB.put(params).promise();
return {
statusCode: 200,
body: JSON.stringify({ message: 'User signed up successfully!' }),
};
} catch (error) {
console.error('Error saving user:', error);
return {
statusCode: 500,
body: JSON.stringify({ message: 'User sign-up failed.' }),
};
}
};

In this function, I handle potential errors and provide appropriate responses based on the
operation's success or failure.

Scenario 42: Using Edge Computing

Your company is exploring edge computing to reduce latency for users in different
geographic locations. You decide to implement a feature using Cloudflare Workers to cache
API responses.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
571

Question: How would you set up a Cloudflare Worker to cache responses, and what benefits
would this bring?

Answer:
Setting up a Cloudflare Worker to cache API responses involves creating a worker script that
intercepts requests, fetches data from the original API, and stores it in the cache. This
reduces latency and improves response times for users accessing the data from various
locations.

The worker should check the cache before fetching the API data and store the response for
subsequent requests. This caching strategy can significantly enhance performance and
reduce load on the original API, especially for frequently requested data.

For Example:
Here’s a basic implementation of a Cloudflare Worker to cache API responses:

addEventListener('fetch', event => {


event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {


const cacheKey = new Request(request.url, { cf: { cacheKey: request.url } });
const cache = caches.default;

// Check cache
let response = await cache.match(cacheKey);
if (!response) {
response = await fetch(request);
// Cache the response for future requests
event.waitUntil(cache.put(cacheKey, response.clone()));
}
return response;
}

In this example, the worker checks the cache first. If the response is not cached, it fetches it
from the API, caches it, and then returns it to the client.

Scenario 43: IoT Development

You are working on an IoT project where you need to collect data from various sensors and
send it to a central server using JavaScript.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
572

Question: What technologies or libraries would you use for this IoT project, and how would
you implement data transmission?

Answer:
For an IoT project collecting data from sensors and transmitting it to a server, I would use the
Johnny-Five library, which is a JavaScript framework for robotics and IoT. It allows you to
easily interface with various hardware components.

To implement data transmission, I would use the HTTP module or a WebSocket connection
to send sensor data to a central server. WebSockets are particularly useful for real-time
communication, allowing continuous data flow without the overhead of repeated HTTP
requests.

For Example:
Here’s how you might set up a simple IoT device using Johnny-Five to collect data and send
it to a server:

const { Board, Sensor } = require('johnny-five');


const axios = require('axios');
const board = new Board();

board.on('ready', () => {
const temperatureSensor = new Sensor('A0');

temperatureSensor.on('data', async () => {


const temperature = this.value; // Get sensor value
try {
await axios.post('https://example.com/api/sensors', { temperature });
console.log('Temperature sent:', temperature);
} catch (error) {
console.error('Error sending data:', error);
}
});
});

In this example, the temperature sensor data is collected and sent to a server endpoint using
Axios.

Scenario 44: AI and Machine Learning with JavaScript

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
573

You are developing a web application that requires image classification capabilities using
JavaScript. You decide to use TensorFlow.js.

Question: How would you implement image classification in your application using
TensorFlow.js?

Answer:
To implement image classification using TensorFlow.js, I would first load a pre-trained model,
such as MobileNet, which is suitable for image classification tasks. This allows the application
to leverage existing models without needing to train one from scratch.

The workflow would include capturing the image input from the user, processing the image
to fit the model's expected input shape, and then passing the image to the model for
classification. Finally, I would display the classification results to the user.

For Example:
Here’s a basic implementation of image classification using TensorFlow.js:

async function classifyImage(imageElement) {


const model = await mobilenet.load();
const predictions = await model.classify(imageElement);
predictions.forEach(prediction => {
console.log(`Prediction: ${prediction.className}, Probability:
${prediction.probability}`);
});
}

// Usage
const img = document.getElementById('myImage');
classifyImage(img);

In this example, the classifyImage function loads the MobileNet model, classifies the
provided image element, and logs the predictions.

Scenario 45: WebAssembly Integration

You are tasked with optimizing a web application by integrating a WebAssembly module to
perform heavy computations.

Question: How would you create and use a WebAssembly module in your JavaScript
application?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
574

Answer:
To create and use a WebAssembly (Wasm) module, I would write the computationally
intensive code in a language that compiles to WebAssembly, such as C or Rust. After
compiling the code, I would load the Wasm module in my JavaScript application and invoke
its functions to perform the required computations.

This integration can lead to significant performance improvements, especially for tasks
involving large data processing or complex calculations, as WebAssembly executes at near-
native speed.

For Example:
Here’s how to load and use a WebAssembly module in JavaScript:

async function loadWasmModule() {


const response = await fetch('module.wasm');
const bytes = await response.arrayBuffer();
const { instance } = await WebAssembly.instantiate(bytes);
const result = instance.exports.myFunction(5); // Call exported function
console.log('Result from WebAssembly:', result);
}

loadWasmModule();

In this example, the WebAssembly module is loaded, and a function is called, showcasing
how to leverage Wasm in a JavaScript application.

Scenario 46: Progressive Web Apps

You are developing a Progressive Web App (PWA) that should work offline and provide a
smooth user experience.

Question: What steps would you take to ensure your web application behaves like a PWA,
and how would you implement a service worker?

Answer:
To ensure my web application behaves like a Progressive Web App, I would follow these
steps:

1. Use HTTPS: Serve the application over HTTPS for security.


2. Create a Web App Manifest: This JSON file provides metadata about the app, such as
name, icons, and theme color.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
575

3. Implement a Service Worker: The service worker manages caching and enables
offline functionality by intercepting network requests.

The service worker should cache essential assets during the installation phase and serve
cached content when the user is offline.

For Example:
Here’s how to implement a service worker for a PWA:

self.addEventListener('install', (event) => {


event.waitUntil(
caches.open('v1').then((cache) => {
return cache.addAll(['/', '/index.', '/styles.css']);
})
);
});

self.addEventListener('fetch', (event) => {


event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});

In this example, the service worker caches the specified files and serves them when offline,
providing a seamless experience for the user.

Scenario 47: Using Modern Frontend Tools

You are building a new React application and want to improve your development experience
by using modern tools.

Question: What tools would you choose for your frontend development setup, and how
would they benefit your workflow?

Answer:
For building a new React application, I would choose tools like Vite, Next.js, and TurboRepo.
Each tool provides specific advantages that enhance the development workflow:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
576

1. Vite: This build tool offers fast development and hot module replacement (HMR),
making it ideal for React development. Its optimized build process improves initial
load times.
2. Next.js: It provides built-in server-side rendering (SSR) capabilities, which enhances
SEO and performance. It also simplifies routing and static site generation.
3. TurboRepo: This tool helps manage monorepos effectively, allowing for efficient
builds and shared code across multiple projects.

For Example:
When creating a new project with Vite, I would run:

npm create vite@latest my-react-app --template react

This command sets up a new React project with Vite, optimizing my development
experience right from the start.

Scenario 48: Integrating AI with Brain.js

You are tasked with creating a simple neural network to predict user behavior on your
website using Brain.js.

Question: How would you set up a basic neural network with Brain.js for this task?

Answer:
To create a simple neural network with Brain.js, I would first install the Brain.js library and
then define the architecture of the network based on the input data I want to train it on. This
could include user interaction metrics like time spent on the site, click patterns, etc.

The network can be trained using historical data, and after training, it can be used to make
predictions about future user behavior based on new input data.

For Example:
Here’s how to set up a basic neural network using Brain.js:

const brain = require('brain.js');


const net = new brain.NeuralNetwork();

net.train([

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
577

{ input: [0, 0], output: [0] },


{ input: [0, 1], output: [1] },
{ input: [1, 0], output: [1] },
{ input: [1, 1], output: [0] },
]);

const output = net.run([1, 0]); // Example input


console.log(output); // Output could be close to 1

In this example, the neural network is trained on a simple XOR pattern, and the trained
model can be used for making predictions.

Scenario 49: Implementing WebAssembly and JavaScript Interoperability

You have a performance-critical algorithm written in C that needs to be integrated into your
JavaScript application using WebAssembly.

Question: What steps would you take to compile the C code to WebAssembly, and how
would you use it in your JavaScript application?

Answer:
To compile C code to WebAssembly, I would use a tool like Emscripten, which allows C/C++
code to be compiled into WebAssembly. The steps involved are:

1. Set up Emscripten: Install Emscripten and configure the environment.


2. Compile the C code: Use the Emscripten compiler to generate a .wasm file from the C
code.
3. Load the WebAssembly module in JavaScript: Use the WebAssembly API to load the
compiled module and call its exported functions.

This integration allows performance-critical code to be executed at near-native speed within


the JavaScript environment.

For Example:
Here’s how to load and use the compiled WebAssembly module:

async function loadWasm() {


const response = await fetch('module.wasm');
const bytes = await response.arrayBuffer();
const { instance } = await WebAssembly.instantiate(bytes);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
578

const result = instance.exports.yourFunction(5); // Call your C function


console.log('Result from WebAssembly:', result);
}

loadWasm();

In this example, the WebAssembly module is loaded, and a function from the compiled C
code is called in the JavaScript application.

Scenario 50: Optimizing Performance for a Large-Scale Application

You are developing a large-scale web application and need to optimize its performance to
handle a significant number of users efficiently.

Question: What strategies would you implement to optimize the performance of your large-
scale application?

Answer:
To optimize the performance of a large-scale web application, I would implement the
following strategies:

1. Code Splitting: Use dynamic imports and techniques like lazy loading to reduce initial
load times and only load necessary code for the user’s current view.
2. Caching: Implement caching strategies both on the client-side (using service
workers) and server-side to reduce server load and improve response times.
3. Optimize Images and Assets: Use modern formats (like WebP) and responsive image
techniques to minimize file sizes without sacrificing quality.
4. Performance Monitoring: Utilize tools like Google Lighthouse and performance APIs
to continuously monitor and identify bottlenecks in the application.
5. CDN Usage: Distribute static assets through a Content Delivery Network (CDN) to
serve users from the nearest geographic location.

For Example:
Here’s how to implement code splitting in a React application:

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function App() {
return (
<React.Suspense fallback={<div>Loading...</div>}>

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
579

<OtherComponent />
</React.Suspense>
);
}

In this example, OtherComponent is loaded only when needed, optimizing performance by


reducing the initial bundle size.

Scenario 51: Handling Form Submission

You are developing a web form that collects user information. When the form is submitted,
you need to validate the input and display an error message if any field is empty.

Question: How would you implement form validation using JavaScript?

Answer:
To implement form validation in JavaScript, I would create an event listener for the form's
submit event. In this listener, I would check if the required fields are filled out. If any fields are
empty, I would prevent the form from being submitted and display an error message to the
user.

For Example:
Here’s how the validation could be set up:

document.getElementById('myForm').addEventListener('submit', function(event) {
const name = document.getElementById('name').value;
const email = document.getElementById('email').value;

if (!name || !email) {
event.preventDefault(); // Prevent form submission
alert('Please fill in all fields.');
}
});

In this example, the form submission is prevented if any fields are empty, ensuring that the
user provides all necessary information.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
580

Scenario 52: Dynamic Content Loading

You need to load user data dynamically into a web page after it has been rendered without
refreshing the page.

Question: What approach would you take to fetch and display this data using JavaScript?

Answer:
To load user data dynamically without refreshing the page, I would use the Fetch API to
make an asynchronous request to the server. Once the data is retrieved, I would manipulate
the DOM to display the data in the appropriate section of the web page.

For Example:
Here’s how this could be implemented:

async function loadUserData() {


try {
const response = await fetch('https://api.example.com/users');
const data = await response.json();

const userContainer = document.getElementById('userContainer');


userContainer.innerHTML = data.map(user => `<p>${user.name}</p>`).join('');
} catch (error) {
console.error('Error loading user data:', error);
}
}

// Call the function to load user data


loadUserData();

In this example, user data is fetched from an API, and the results are displayed in the
specified container on the page.

Scenario 53: Handling API Responses

You are working on a feature that fetches data from an external API. You need to ensure that
the application handles both successful and unsuccessful responses appropriately.

Question: How would you manage API responses in your application using JavaScript?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
581

Answer:
To manage API responses effectively, I would use the Fetch API along with promise chaining
to handle both success and error cases. Upon receiving a response, I would check the HTTP
status code to determine whether the request was successful or if there was an error. Based
on the result, I would update the UI accordingly.

For Example:
Here’s an example of handling API responses:

fetch('https://api.example.com/data')
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok ' + response.statusText);
}
return response.json();
})
.then(data => {
console.log('Data received:', data);
// Update the UI with the data
})
.catch(error => {
console.error('Error fetching data:', error);
// Display error message to the user
});

In this example, the application checks the response status and handles errors gracefully
while updating the UI with the received data.

Scenario 54: Implementing a Simple Timer

You are building a web application that requires a simple timer to track how long users
spend on a specific page.

Question: How would you implement a timer using JavaScript that updates every second?

Answer:
To implement a simple timer, I would use setInterval() to create a timer that updates
every second. The timer would keep track of the elapsed time in seconds and update the
displayed time in the UI.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
582

For Example:
Here’s how this could be implemented:

let seconds = 0;
const timerDisplay = document.getElementById('timerDisplay');

const timer = setInterval(() => {


seconds++;
timerDisplay.textContent = `Time spent: ${seconds} seconds`;
}, 1000);

// To stop the timer after a certain condition, you can call clearInterval(timer);

In this example, the timer increments every second and updates the display to show the
elapsed time.

Scenario 55: Filtering Data in a List

You have a list of items displayed on a web page, and you want to implement a search
feature that filters the list based on user input.

Question: How would you create a filter function using JavaScript to search through the list?

Answer:
To create a filter function for searching through a list, I would attach an event listener to the
search input field. This listener would trigger a function that filters the displayed items based
on the user’s input and updates the visible list accordingly.

For Example:
Here’s how to implement this filter functionality:

const items = ['Apple', 'Banana', 'Cherry', 'Date'];


const itemList = document.getElementById('itemList');

function filterItems() {
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
const filteredItems = items.filter(item =>
item.toLowerCase().includes(searchTerm));

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
583

itemList.innerHTML = filteredItems.map(item => `<li>${item}</li>`).join('');


}

document.getElementById('searchInput').addEventListener('input', filterItems);

In this example, the list of items is filtered in real-time based on the user’s input, providing a
dynamic search experience.

Scenario 56: Validating User Input

You are developing a user registration form that requires specific validation rules for email
and password fields.

Question: What approach would you take to validate these fields using JavaScript?

Answer:
To validate the email and password fields, I would create a function that checks if the email
format is correct using a regular expression. Additionally, I would check that the password
meets specific criteria, such as length and character requirements. Upon submission, I would
prevent the form from being submitted if validation fails and display error messages.

For Example:
Here’s how you might implement the validation:

document.getElementById('registrationForm').addEventListener('submit',
function(event) {
const email = document.getElementById('email').value;
const password = document.getElementById('password').value;
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

let valid = true;


let messages = [];

if (!emailRegex.test(email)) {
valid = false;
messages.push('Please enter a valid email address.');
}

if (password.length < 8) {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
584

valid = false;
messages.push('Password must be at least 8 characters long.');
}

if (!valid) {
event.preventDefault(); // Prevent form submission
alert(messages.join('\n'));
}
});

In this example, validation checks are performed on the email and password fields, with
relevant error messages displayed if the input is invalid.

Scenario 57: Debouncing Input Events

You are implementing a search feature that triggers a search function as the user types.
However, you want to prevent the search function from being called too frequently.

Question: How would you implement debouncing in this scenario using JavaScript?

Answer:
To implement debouncing, I would create a debounce function that limits how often the
search function is called as the user types. The debounce function would delay the execution
of the search function until the user has stopped typing for a specified period.

For Example:
Here’s how to implement debouncing for the search input:

function debounce(func, delay) {


let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), delay);
};
}

const performSearch = debounce(function() {


const query = document.getElementById('searchInput').value;
console.log('Searching for:', query);
// Perform the search operation here

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
585

}, 300);

document.getElementById('searchInput').addEventListener('input', performSearch);

In this example, the search function is called only after the user has stopped typing for 300
milliseconds, improving performance and reducing unnecessary calls.

Scenario 58: Using Promises for Asynchronous Operations

You need to fetch user data from an API and display it on the web page. You want to handle
the asynchronous nature of this operation using promises.

Question: How would you use promises to manage this asynchronous operation?

Answer:
To manage the asynchronous operation of fetching user data using promises, I would use the
Fetch API to request the data and handle the response using .then() and .catch() for
success and error cases. This approach keeps the code organized and manageable.

For Example:
Here’s how this could be implemented:

fetch('https://api.example.com/users')
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
console.log('User data:', data);
// Update the UI with user data
})
.catch(error => {
console.error('Error fetching user data:', error);
// Display error message to the user
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
586

In this example, promises are used to handle the fetching of user data, providing clear paths
for success and error handling.

Scenario 59: Creating a Modal Dialog

You need to create a modal dialog that opens when a button is clicked and closes when the
user clicks outside the modal.

Question: How would you implement this modal functionality using JavaScript?

Answer:
To implement a modal dialog, I would create HTML elements for the modal and overlay, style
them with CSS, and use JavaScript to control their visibility. An event listener on the button
would open the modal, and a click event listener on the overlay would close it.

For Example:
Here’s how this could be set up:

const modal = document.getElementById('myModal');


const btn = document.getElementById('openModal');
const overlay = document.getElementById('overlay');

btn.addEventListener('click', () => {
modal.style.display = 'block';
overlay.style.display = 'block';
});

overlay.addEventListener('click', () => {
modal.style.display = 'none';
overlay.style.display = 'none';
});

In this example, clicking the button opens the modal and overlay, while clicking outside the
modal (on the overlay) closes both.

Scenario 60: Implementing a Simple Todo List

You are tasked with creating a simple todo list application where users can add and remove
tasks.

Question: How would you implement this todo list functionality using JavaScript?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
587

Answer:
To implement a simple todo list application, I would create an HTML form for adding tasks
and a section to display the list of tasks. Using JavaScript, I would manage the list of tasks in
an array and update the DOM dynamically when tasks are added or removed.

For Example:
Here’s how the todo list functionality could be set up:

const tasks = [];


const taskInput = document.getElementById('taskInput');
const taskList = document.getElementById('taskList');

document.getElementById('addTaskButton').addEventListener('click', () => {
const task = taskInput.value;
if (task) {
tasks.push(task);
updateTaskList();
taskInput.value = ''; // Clear the input field
}
});

function updateTaskList() {
taskList.innerHTML = tasks.map((task, index) =>
`<li>${task} <button onclick="removeTask(${index})">Remove</button></li>`
).join('');
}

function removeTask(index) {
tasks.splice(index, 1);
updateTaskList();
}

In this example, users can add tasks to the list, and each task includes a button to remove it,
demonstrating basic CRUD functionality with a todo list.

Scenario 61: Implementing a Pagination System

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
588

You are developing a web application that displays a large list of items, and you need to
implement pagination to improve usability.

Question: How would you implement a pagination system in JavaScript, and what
considerations would you have for performance?

Answer:
To implement a pagination system in JavaScript, I would first determine the total number of
items and the number of items to display per page. Based on this information, I would
calculate the total number of pages and create a mechanism to navigate between them
(e.g., "Previous" and "Next" buttons).

For performance considerations, I would only fetch and render the items needed for the
current page instead of loading all items at once. This can be achieved through API calls that
accept pagination parameters, reducing load times and improving responsiveness.

For Example:
Here’s a basic implementation of a pagination system:

const itemsPerPage = 10;


let currentPage = 1;

async function fetchItems(page) {


const response = await
fetch(`https://api.example.com/items?page=${page}&limit=${itemsPerPage}`);
const data = await response.json();
return data.items; // Assuming the API returns items in this format
}

function renderItems(items) {
const itemList = document.getElementById('itemList');
itemList.innerHTML = items.map(item => `<li>${item.name}</li>`).join('');
}

async function loadPage(page) {


const items = await fetchItems(page);
renderItems(items);
}

document.getElementById('nextButton').addEventListener('click', () => {
currentPage++;
loadPage(currentPage);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
589

});

document.getElementById('prevButton').addEventListener('click', () => {
if (currentPage > 1) {
currentPage--;
loadPage(currentPage);
}
});

// Initial load
loadPage(currentPage);

In this example, the application fetches items for the current page, rendering only what’s
necessary and minimizing network requests.

Scenario 62: Managing Complex State with useReducer

You are building a React application that requires managing complex state with multiple
actions, such as incrementing, decrementing, and resetting a counter.

Question: How would you use the useReducer hook to manage this complex state
effectively?

Answer:
To manage complex state in a React application, I would use the useReducer hook, which is
suited for handling state transitions based on different actions. I would define an initial state
and a reducer function that specifies how the state should change in response to dispatched
actions.

Using useReducer provides clarity and structure, especially when dealing with multiple state
updates that depend on each other.

For Example:
Here’s how to set this up using useReducer:

import React, { useReducer } from 'react';

const initialState = { count: 0 };

function reducer(state, action) {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
590

switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return initialState;
default:
throw new Error();
}
}

function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);

return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment'
})}>Increment</button>
<button onClick={() => dispatch({ type: 'decrement'
})}>Decrement</button>
<button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
</div>
);
}

In this example, the useReducer hook effectively manages the counter's state based on the
dispatched actions, providing clear and maintainable code.

Scenario 63: Handling Authentication with JWT

You are building an application that requires user authentication. You decide to use JSON
Web Tokens (JWT) for managing user sessions.

Question: How would you implement JWT authentication in your application, and what
security measures would you take?

Answer:
To implement JWT authentication, I would first create an API endpoint for user login that
validates the user’s credentials. Upon successful authentication, the server would generate a
JWT and send it back to the client. The client would store the JWT (usually in local storage or

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
591

a cookie) and include it in the Authorization header of subsequent requests to protected


routes.

Security measures would include:

1. HTTPS: Always use HTTPS to encrypt data in transit.


2. Expiration: Set a reasonable expiration time for the JWT to limit its validity.
3. Refresh Tokens: Implement a refresh token mechanism to allow users to maintain
their sessions without re-entering credentials frequently.

For Example:
Here’s a simplified implementation of JWT authentication:

// Server-side login route


app.post('/login', (req, res) => {
const user = authenticate(req.body); // Implement user authentication
if (user) {
const token = jwt.sign({ id: user.id }, 'your_secret_key', { expiresIn:
'1h' });
res.json({ token });
} else {
res.status(401).send('Unauthorized');
}
});

// Client-side request with token


async function fetchProtectedData() {
const token = localStorage.getItem('token'); // Retrieve token from storage
const response = await fetch('https://api.example.com/protected', {
headers: {
'Authorization': `Bearer ${token}`,
},
});
const data = await response.json();
console.log(data);
}

In this example, the server generates a JWT upon successful login, and the client uses the
token to access protected resources.

Scenario 64: Creating a Custom Hook

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
592

You need to manage form inputs across multiple components in a React application. To
improve reusability, you decide to create a custom hook.

Question: How would you implement a custom hook for form handling in your application?

Answer:
To create a custom hook for form handling, I would define a hook that manages the form
state and provides handlers for input changes. This hook would return the current form
values and the functions to handle input updates, making it easy to reuse across different
components.

For Example:
Here’s how to implement a custom hook for managing form inputs:

import { useState } from 'react';

function useForm(initialValues) {
const [values, setValues] = useState(initialValues);

const handleChange = (event) => {


const { name, value } = event.target;
setValues({
...values,
[name]: value,
});
};

return {
values,
handleChange,
};
}

// Usage in a component
function MyForm() {
const { values, handleChange } = useForm({ username: '', email: '' });

const handleSubmit = (event) => {


event.preventDefault();
console.log('Form submitted:', values);
};

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
593

return (
<form onSubmit={handleSubmit}>
<input name="username" value={values.username} onChange={handleChange}
/>
<input name="email" value={values.email} onChange={handleChange} />
<button type="submit">Submit</button>
</form>
);
}

In this example, the useForm custom hook manages form state and simplifies handling input
changes, improving code reusability.

Scenario 65: Error Boundaries in React

You are developing a React application where some components may throw errors during
rendering. You want to ensure the application remains stable and provides feedback to users.

Question: How would you implement error boundaries in your React application?

Answer:
To implement error boundaries in a React application, I would create a higher-order
component (HOC) or a class component that utilizes the componentDidCatch lifecycle
method. This component would catch JavaScript errors in its child component tree, log the
error, and render a fallback UI instead of crashing the whole application.

Error boundaries can be implemented only in class components, so I would create a


dedicated error boundary class.

For Example:
Here’s how to create an error boundary:

class ErrorBoundary extends React.Component {


constructor(props) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError(error) {
return { hasError: true }; // Update state to indicate an error occurred

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
594

componentDidCatch(error, info) {
console.error('Error caught by ErrorBoundary:', error, info);
}

render() {
if (this.state.hasError) {
return <h1>Something went wrong. Please try again later.</h1>;
}

return this.props.children; // Render child components if no error


}
}

// Usage
function App() {
return (
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
);
}

In this example, the ErrorBoundary component catches errors in its child components and
provides a user-friendly error message, improving the application's robustness.

Scenario 66: Optimizing Performance with React.memo

You have a React application with a large number of components that re-render frequently.
You want to optimize the performance by preventing unnecessary re-renders.

Question: How would you use React.memo to improve the performance of your components?

Answer:
To optimize performance in a React application, I would use React.memo, a higher-order
component that prevents functional components from re-rendering when their props do not
change. This is especially useful for components that receive complex objects or arrays as
props.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
595

By wrapping components with React.memo, I can ensure they only re-render when their
relevant props change, reducing the rendering workload and improving application
responsiveness.

For Example:
Here’s how to implement React.memo:

const MyComponent = React.memo(({ data }) => {


console.log('Rendering MyComponent');
return <div>{data}</div>;
});

// Parent component
function Parent() {
const [count, setCount] = useState(0);
const data = "Hello, World!";

return (
<div>
<MyComponent data={data} />
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}

In this example, MyComponent will only re-render when its data prop changes, allowing the
parent component to update without affecting the child unnecessarily.

Scenario 67: State Management with Context API

You need to manage global state across multiple components in your React application. You
decide to use the Context API instead of a state management library.

Question: How would you implement state management using the Context API in your
application?

Answer:
To implement state management using the Context API in React, I would create a context
using React.createContext() and provide a state and updater function through the

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
596

context provider. This allows any nested components to access and update the global state
without prop drilling.

I would create a context provider component that wraps the application, allowing all child
components to consume the context values.

For Example:
Here’s how to set up the Context API for state management:

const MyContext = React.createContext();

function MyProvider({ children }) {


const [state, setState] = useState({ user: null });

return (
<MyContext.Provider value={{ state, setState }}>
{children}
</MyContext.Provider>
);
}

// Usage in a component
function UserProfile() {
const { state, setState } = useContext(MyContext);

const login = () => {


setState({ user: { name: 'John Doe' } });
};

return (
<div>
<p>User: {state.user ? state.user.name : 'Not logged in'}</p>
<button onClick={login}>Login</button>
</div>
);
}

// Wrap the application in MyProvider


function App() {
return (
<MyProvider>
<UserProfile />

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
597

</MyProvider>
);
}

In this example, the Context API allows for global state management without prop drilling,
making it easy for components to access and modify shared state.

Scenario 68: Using Local Storage for Data Persistence

You are developing a web application that requires user preferences to be saved even after
the browser is closed.

Question: How would you use Local Storage in your application to store and retrieve user
preferences?

Answer:
To use Local Storage for data persistence in a web application, I would utilize the
localStorage API, which allows storing key-value pairs in the browser. I would save user
preferences (like theme or language) whenever they change, and retrieve these preferences
on page load to set the initial state.

Local Storage persists data even after the browser is closed, making it ideal for storing user
settings.

For Example:
Here’s how to implement Local Storage for user preferences:

// Save preference
function savePreference(preference) {
localStorage.setItem('userPreference', JSON.stringify(preference));
}

// Load preference on page load


function loadPreference() {
const savedPreference = localStorage.getItem('userPreference');
return savedPreference ? JSON.parse(savedPreference) : null;
}

// Usage
const userPreference = loadPreference();

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
598

if (userPreference) {
console.log('Loaded user preference:', userPreference);
}

// Example of saving preference


const newPreference = { theme: 'dark' };
savePreference(newPreference);

In this example, user preferences are saved to Local Storage and retrieved on page load,
allowing for persistence across sessions.

Scenario 69: Throttling Scroll Events

You are building a feature that triggers actions based on the user's scroll position, but you
want to limit how often these actions are executed to improve performance.

Question: How would you implement throttling for scroll events in your application?

Answer:
To implement throttling for scroll events, I would create a throttle function that limits how
often the event handler can be executed. This can be accomplished by tracking the time of
the last execution and only allowing the handler to run if a certain interval has passed.

Throttling ensures that the scroll event handler is executed at most once every specified
interval, preventing excessive calls and improving performance.

For Example:
Here’s how to implement throttling:

function throttle(func, limit) {


let lastFunc;
let lastRan;

return function() {
const context = this;
const args = arguments;

if (!lastRan) {
func.apply(context, args);
lastRan = Date.now();

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
599

} else {
clearTimeout(lastFunc);
lastFunc = setTimeout(function() {
if ((Date.now() - lastRan) >= limit) {
func.apply(context, args);
lastRan = Date.now();
}
}, limit - (Date.now() - lastRan));
}
};
}

window.addEventListener('scroll', throttle(() => {


console.log('Scroll event triggered!');
}, 1000));

In this example, the scroll event is throttled to execute at most once every second, improving
the efficiency of the scroll handling logic.

Scenario 70: Building a Responsive Navigation Menu

You are tasked with creating a responsive navigation menu that collapses into a hamburger
menu on smaller screens.

Question: How would you implement this responsive navigation using JavaScript and CSS?

Answer:
To create a responsive navigation menu, I would use CSS media queries to style the menu for
different screen sizes. JavaScript would be used to toggle the visibility of the menu when the
hamburger icon is clicked.

The implementation involves setting up the HTML structure for the navigation menu and
adding event listeners for the toggle functionality.

For Example:
Here’s how this could be implemented:

<nav>
<div class="hamburger" id="hamburger">☰</div>
<ul class="nav-links" id="navLinks">

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
600

<li><a href="#">Home</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>

<style>
.nav-links {
display: flex;
}
.hamburger {
display: none;
}

@media (max-width: 768px) {


.nav-links {
display: none; /* Hide nav links by default */
flex-direction: column;
}
.hamburger {
display: block; /* Show hamburger icon */
}
}
</style>

<script>
document.getElementById('hamburger').addEventListener('click', () => {
const navLinks = document.getElementById('navLinks');
navLinks.style.display = navLinks.style.display === 'flex' ? 'none' : 'flex';
});
</script>

In this example, the navigation menu adapts to screen size, and the hamburger icon toggles
the visibility of the menu items, providing a responsive design that enhances user
experience.

Scenario 71: Optimizing API Calls

You are building a dashboard that displays data from multiple APIs. Users can filter the data
based on different criteria, which results in multiple API calls.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
601

Question: How would you optimize API calls to improve performance and reduce
unnecessary network requests?

Answer:
To optimize API calls in a dashboard application, I would implement a strategy that includes
batching requests, caching responses, and utilizing debouncing techniques for input
changes. Batching allows me to combine multiple API requests into a single call whenever
possible.

Caching responses using local storage or in-memory data structures would prevent
redundant calls for frequently requested data. Additionally, I would debounce the input fields
to delay the API call until the user has stopped typing for a specified duration, reducing the
number of requests sent to the server.

For Example:
Here’s how you might implement debouncing for API calls in a search feature:

let timeout;

function fetchData(query) {
// Assume fetchDataFromAPI is a function that fetches data based on the query
fetchDataFromAPI(query).then(data => {
console.log(data);
});
}

const handleSearch = (event) => {


clearTimeout(timeout);
const query = event.target.value;

timeout = setTimeout(() => {


fetchData(query);
}, 300); // Delay API call by 300ms
};

document.getElementById('searchInput').addEventListener('input', handleSearch);

In this example, the API call is delayed until the user stops typing, optimizing network usage
and improving performance.

Scenario 72: Implementing Infinite Scrolling

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
602

You need to implement infinite scrolling on a webpage that loads additional content as the
user scrolls down.

Question: How would you set up infinite scrolling using JavaScript, and what considerations
should you have for performance?

Answer:
To implement infinite scrolling, I would listen for the scroll event on the window and check if
the user has scrolled near the bottom of the page. When the bottom is reached, I would fetch
additional data from the server and append it to the existing content.

Performance considerations include debouncing the scroll event listener to limit the
frequency of function calls and handling loading states to prevent multiple simultaneous
requests.

For Example:
Here’s how you might implement infinite scrolling:

let currentPage = 1;
const loading = false;

async function loadMoreItems() {


if (loading) return; // Prevent multiple simultaneous requests
loading = true;

const response = await


fetch(`https://api.example.com/items?page=${currentPage}`);
const data = await response.json();

const container = document.getElementById('itemContainer');


data.items.forEach(item => {
const div = document.createElement('div');
div.textContent = item.name;
container.appendChild(div);
});

currentPage++;
loading = false;
}

window.addEventListener('scroll', () => {
if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 100) {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
603

loadMoreItems(); // Trigger loading more items when near the bottom


}
});

In this example, additional items are loaded as the user scrolls down, enhancing the user
experience by seamlessly loading more content.

Scenario 73: Managing Multiple Side Effects in React

You are developing a React component that fetches data and updates the document title
based on the data retrieved. You need to ensure these side effects are managed properly.

Question: How would you handle multiple side effects in a React component using hooks?

Answer:
To manage multiple side effects in a React component, I would use the useEffect hook to
perform the necessary actions such as data fetching and updating the document title. By
structuring the useEffect calls appropriately, I can ensure that each side effect is managed
independently.

I would also make sure to handle cleanup if necessary, especially when working with
subscriptions or event listeners, to prevent memory leaks.

For Example:
Here’s how to implement this:

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

function MyComponent() {
const [data, setData] = useState(null);

useEffect(() => {
const fetchData = async () => {
const response = await fetch('https://api.example.com/data');
const result = await response.json();
setData(result);
};

fetchData();
}, []); // Empty dependency array to run once on mount

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
604

useEffect(() => {
if (data) {
document.title = data.title; // Update document title based on data
}
}, [data]); // Run whenever data changes

return <div>{data ? data.content : 'Loading...'}</div>;


}

In this example, two separate useEffect hooks manage fetching data and updating the
document title, ensuring clear and effective side effect management.

Scenario 74: Building a Notification System

You are tasked with implementing a notification system that displays alerts to users based
on certain actions within the application.

Question: How would you design and implement this notification system in JavaScript?

Answer:
To build a notification system, I would create a central notification manager that maintains
the state of notifications. This manager would provide methods for adding and removing
notifications, as well as rendering them in the UI.

I would use a component to display notifications, which could be styled appropriately.


Notifications can be temporary (auto-dismissed after a few seconds) or persistent based on
user actions.

For Example:
Here’s a simple implementation of a notification system:

const notifications = [];

function addNotification(message) {
notifications.push(message);
renderNotifications();
}

function removeNotification(index) {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
605

notifications.splice(index, 1);
renderNotifications();
}

function renderNotifications() {
const notificationContainer = document.getElementById('notificationContainer');
notificationContainer.innerHTML = notifications.map((msg, index) =>
`<div class="notification">${msg} <button
onclick="removeNotification(${index})">Dismiss</button></div>`
).join('');
}

// Usage
addNotification('You have a new message!');

In this example, notifications are managed through an array, and the UI is updated to display
the current notifications. Users can dismiss notifications with a button.

Scenario 75: Using Web Workers for Background Processing

You have a web application that requires heavy computations without blocking the UI
thread.

Question: How would you utilize Web Workers to handle background processing in your
application?

Answer:
To use Web Workers for background processing, I would create a separate JavaScript file for
the worker and initialize it in the main application file. Web Workers run in a separate thread,
allowing for heavy computations to be performed without freezing the UI.

I would communicate with the worker using the postMessage method to send data and
listen for results with the onmessage event handler.

For Example:
Here’s how to set up a Web Worker:

worker.js

self.onmessage = function(event) {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
606

const result = heavyComputation(event.data);


self.postMessage(result);
};

function heavyComputation(data) {
// Perform heavy computation
return data * 2; // Example computation
}

main.js

const worker = new Worker('worker.js');

worker.onmessage = function(event) {
console.log('Result from worker:', event.data);
};

worker.postMessage(10); // Send data to the worker for processing

In this example, heavy computations are offloaded to a Web Worker, improving the
performance and responsiveness of the main application.

Scenario 76: Implementing a Drag-and-Drop Feature

You need to implement a drag-and-drop feature in your web application that allows users to
rearrange items in a list.

Question: How would you implement this drag-and-drop functionality using JavaScript?

Answer:
To implement drag-and-drop functionality, I would use the HTML5 Drag and Drop API. I
would attach event listeners for drag events (such as dragstart, dragover, and drop) to the
list items.

During the drag operation, I would maintain the state of the item being dragged and update
the list order upon dropping the item in a new position.

For Example:
Here’s a basic implementation of drag-and-drop:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
607

<ul id="draggableList">
<li draggable="true">Item 1</li>
<li draggable="true">Item 2</li>
<li draggable="true">Item 3</li>
</ul>

<script>
const listItems = document.querySelectorAll('#draggableList li');
let draggedItem;

listItems.forEach(item => {
item.addEventListener('dragstart', () => {
draggedItem = item;
});

item.addEventListener('dragover', (event) => {


event.preventDefault(); // Allow drop
});

item.addEventListener('drop', () => {
if (item !== draggedItem) {
const list = document.getElementById('draggableList');
const draggedIndex = Array.from(listItems).indexOf(draggedItem);
const targetIndex = Array.from(listItems).indexOf(item);

if (draggedIndex < targetIndex) {


item.after(draggedItem);
} else {
item.before(draggedItem);
}
}
});
});
</script>

In this example, users can drag and drop items in a list, and the order is updated accordingly.

Scenario 77: Implementing Theming with CSS Variables

You are tasked with allowing users to toggle between light and dark themes in your
application.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
608

Question: How would you implement theming using CSS variables in JavaScript?

Answer:
To implement theming using CSS variables, I would define the color schemes for both light
and dark themes in the CSS. Upon toggling the theme, I would update the CSS variables in
the :root selector through JavaScript.

This approach allows for dynamic theme changes without reloading the page, providing a
smooth user experience.

For Example:
Here’s how this could be implemented:

styles.css

:root {
--background-color: white;
--text-color: black;
}

body {
background-color: var(--background-color);
color: var(--text-color);
}

.dark-theme {
--background-color: black;
--text-color: white;
}

script.js

document.getElementById('themeToggle').addEventListener('click', () => {
document.body.classList.toggle('dark-theme');
});

In this example, toggling the theme class on the body element updates the CSS variables,
changing the appearance of the application.

Scenario 78: Implementing a Rating System

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
609

You are building a product review system where users can rate products using stars.

Question: How would you implement this rating system using JavaScript?

Answer:
To implement a rating system using stars, I would create an interactive UI with star icons that
users can click on to select their rating. I would use event listeners to handle clicks and
update the state based on the user's selection.

The selected rating would be stored in a variable, and I would visually indicate the current
rating by filling the stars.

For Example:
Here’s how to set up a simple rating system:

<div id="rating">
<span class="star" data-value="1">★</span>
<span class="star" data-value="2">★</span>
<span class="star" data-value="3">★</span>
<span class="star" data-value="4">★</span>
<span class="star" data-value="5">★</span>
</div>

<script>
const stars = document.querySelectorAll('.star');

stars.forEach(star => {
star.addEventListener('click', () => {
const rating = star.getAttribute('data-value');
stars.forEach(s => s.classList.remove('selected'));
for (let i = 0; i < rating; i++) {
stars[i].classList.add('selected'); // Fill stars based on rating
}
console.log(`User rated: ${rating} stars`);
});
});
</script>

<style>
.star {
cursor: pointer;

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
610

font-size: 30px;
}
.star.selected {
color: gold; // Filled star color
}
</style>

In this example, clicking on a star updates the visual representation of the rating and logs the
user's choice.

Scenario 79: Creating a Custom Select Dropdown

You need to create a custom dropdown menu that enhances the standard HTML <select>
element with better styling and functionality.

Question: How would you implement a custom select dropdown using JavaScript?

Answer:
To create a custom select dropdown, I would use a combination of HTML, CSS, and
JavaScript. I would hide the standard <select> element and create a styled dropdown using
<div> and <ul> elements. Event listeners would handle opening and closing the dropdown
and selecting an item.

This approach provides greater flexibility for styling and behavior compared to native select
elements.

For Example:
Here’s how to implement a custom dropdown:

<div class="custom-select">
<div class="select-selected">Select an option</div>
<div class="select-items select-hide">
<div>Option 1</div>
<div>Option 2</div>
<div>Option 3</div>
</div>
</div>

<style>
.custom-select {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
611

position: relative;
display: inline-block;
}
.select-selected {
background-color: #f1f1f1;
cursor: pointer;
}
.select-items {
position: absolute;
background-color: white;
display: none;
border: 1px solid #ccc;
}
.select-items div {
padding: 8px;
cursor: pointer;
}
.select-items div:hover {
background-color: #ddd;
}
.select-hide {
display: none;
}
</style>

<script>
document.querySelector('.select-selected').addEventListener('click', function() {
this.nextElementSibling.classList.toggle('select-hide');
});

document.querySelectorAll('.select-items div').forEach(item => {


item.addEventListener('click', function() {
const selected = document.querySelector('.select-selected');
selected.textContent = this.textContent;
this.parentElement.classList.add('select-hide'); // Hide dropdown
});
});
</script>

In this example, the custom dropdown menu allows users to select an option while providing
a more styled and interactive experience compared to standard dropdowns.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
612

Scenario 80: Creating a Simple Chat Application

You are tasked with developing a simple chat application where users can send and receive
messages in real-time.

Question: How would you implement this chat application using JavaScript and
WebSockets?

Answer:
To implement a simple chat application, I would use WebSockets to enable real-time
communication between the client and server. The server would handle incoming messages
and broadcast them to all connected clients.

On the client side, I would establish a WebSocket connection, handle sending messages, and
display received messages dynamically.

For Example:
Here’s a basic implementation using WebSockets:

server.js (Node.js WebSocket server)

const WebSocket = require('ws');


const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', ws => {
ws.on('message', message => {
// Broadcast the received message to all clients
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
});
});

client.js (Client-side WebSocket connection)

const socket = new WebSocket('ws://localhost:8080');

socket.onmessage = function(event) {
const message = document.createElement('div');

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
613

message.textContent = event.data; // Display received message


document.getElementById('messages').appendChild(message);
};

document.getElementById('sendButton').addEventListener('click', () => {
const input = document.getElementById('messageInput').value;
socket.send(input); // Send message to server
});

In this example, the WebSocket server broadcasts incoming messages to all connected
clients, enabling real-time chat functionality. The client-side code manages sending and
receiving messages, providing a simple chat experience.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
614

Chapter 11: Security in JavaScript

THEORETICAL QUESTIONS
1. What is Cross-Site Scripting (XSS) in JavaScript?

Answer:
Cross-Site Scripting (XSS) is a vulnerability that allows attackers to inject malicious scripts into
a trusted website, which are then executed in the browser of an unsuspecting user. This
exploit happens when a web application includes untrusted data without proper validation
or escaping. XSS allows attackers to steal session cookies, capture keystrokes, redirect users
to malicious sites, or perform actions on behalf of the victim. XSS is common in web
applications that dynamically generate HTML content from user inputs.

For Example:

// Vulnerable code: Directly inserting unvalidated user input


const userInput = "<script>alert('Hacked!');</script>";
document.getElementById("output").innerHTML = userInput;

// Correct approach: Use DOM methods that escape HTML content


const safeInput = document.createElement("div");
safeInput.textContent = userInput;
document.getElementById("output").appendChild(safeInput);

In the example above, using innerHTML renders the malicious script, leading to an XSS
attack. Using textContent ensures that input is treated as text and not executable code.

2. How does Cross-Site Request Forgery (CSRF) work?

Answer:
Cross-Site Request Forgery (CSRF) tricks an authenticated user into unknowingly executing
actions on a website where they are logged in. Since web browsers automatically send
cookies along with requests, an attacker can exploit this behavior to make unauthorized
requests, such as changing passwords, transferring money, or modifying user data.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
615

For instance, a CSRF attack could occur if a bank allows sensitive transactions through HTTP
POST requests without additional authentication or a CSRF token.

For Example:

// A malicious form to exploit CSRF vulnerability


<form action="https://bank.com/transfer" method="POST">
<input type="hidden" name="amount" value="10000">
<input type="hidden" name="recipient" value="attacker-account">
<button type="submit">Click me to win a prize!</button>
</form>

// Solution: Use CSRF tokens in all sensitive forms


<form action="/transfer" method="POST">
<input type="hidden" name="csrf_token" value="secure_generated_token">
<input type="text" name="amount" placeholder="Enter amount">
<button type="submit">Transfer</button>
</form>

In this example, using a CSRF token ensures the form submission is legitimate. The token
must be validated by the server before processing the request.

3. What is SQL Injection, and how can it affect JavaScript applications?

Answer:
SQL Injection occurs when an attacker injects malicious SQL code into a query, often through
input fields, compromising the database's security. While SQL Injection primarily affects
backend systems, JavaScript-based frontend applications interacting with backend APIs or
databases can expose vulnerabilities if user inputs are not properly validated.

SQL Injection can lead to unauthorized access, data leakage, or even full system compromise.

For Example:

// Unsafe SQL query construction


const user = getUserInput();
const query = `SELECT * FROM users WHERE username = '${user}'`;

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
616

// If the input is 'admin' OR 1=1;--, the query becomes:


SELECT * FROM users WHERE username = 'admin' OR 1=1;--

// Safe approach: Use parameterized queries


const safeQuery = `SELECT * FROM users WHERE username = ?`;
database.execute(safeQuery, [user]);

Using parameterized queries ensures that user input is treated as data and not as part of the
SQL command, preventing SQL Injection attacks.

4. What is JWT (JSON Web Token), and how is it used for securing APIs?

Answer:
JWT (JSON Web Token) is a compact, URL-safe token used for securely transmitting
information between parties. JWTs contain three parts: a header, payload, and signature.
They are commonly used for API authentication and session management. JWTs can store
user information, and once verified, the server can grant access without querying the
database.

For Example:

// Generating a JWT token


const jwt = require('jsonwebtoken');
const token = jwt.sign({ userId: 123 }, 'secretKey', { expiresIn: '1h' });
console.log(token);

// Verifying the token


jwt.verify(token, 'secretKey', (err, decoded) => {
if (err) {
console.log('Token invalid or expired');
} else {
console.log('Decoded data:', decoded);
}
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
617

JWTs are sent as part of the HTTP headers (e.g., Authorization: Bearer <token>). The
server can use the token to validate user identity without needing to manage sessions.

5. What is OAuth, and how does it secure API access?

Answer:
OAuth 2.0 is an authorization framework that allows third-party applications to access user
resources on another service (e.g., Google, GitHub) without sharing the user’s credentials.
OAuth issues an access token after user authentication, which can be used to access the
protected APIs. This framework ensures secure delegation of access.

For Example:

// Client initiates the OAuth process


const authorizeUrl = "https://auth-server.com/authorize";
window.location.href =
`${authorizeUrl}?response_type=token&client_id=CLIENT_ID&redirect_uri=http://localh
ost`;

// After redirect, extract the token


const accessToken = new URLSearchParams(window.location.hash).get('access_token');
console.log('OAuth Access Token:', accessToken);

With OAuth, users authenticate via a third party, and the token limits the application’s access
to only the authorized resources.

6. What are some common types of XSS attacks?

Answer:
The three primary types of XSS attacks are:

1. Stored XSS: The malicious script is stored on the server and executed when a user
views the page.
2. Reflected XSS: The attack is reflected off the server in the response, typically via a
query parameter.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
618

3. DOM-based XSS: The vulnerability lies in client-side JavaScript code that directly
manipulates the DOM based on user input.

For Example:

// DOM-based XSS example


const userInput = "<img src='x' onerror='alert(\"XSS\")'>";
document.getElementById("content").innerHTML = userInput; // Vulnerable

// Safe approach: Use textContent to escape HTML


const safeDiv = document.createElement("div");
safeDiv.textContent = userInput;
document.getElementById("content").appendChild(safeDiv);

Stored and reflected XSS can be prevented by sanitizing input on both the server and client
sides.

7. How can you prevent XSS in JavaScript?

Answer:
To prevent XSS attacks:

1. Sanitize inputs: Ensure all user inputs are validated and sanitized.
2. Escape outputs: Convert HTML characters to safe representations when displaying
user content.
3. Use Content Security Policy (CSP): Restrict the types of scripts allowed to run.
4. Avoid eval() and similar dangerous functions.

For Example:

// Enforcing CSP to block inline scripts


const express = require('express');
const app = express();
app.use((req, res, next) => {
res.setHeader("Content-Security-Policy", "script-src 'self'");
next();
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
619

Using a CSP ensures that only scripts from trusted sources are executed.

8. How do CSRF tokens help prevent CSRF attacks?

Answer:
CSRF tokens prevent attackers from executing unauthorized actions by associating a unique,
unpredictable token with each session or request. The server validates the token with every
sensitive request to confirm it originated from the legitimate user.

For Example:

// Server-side code to generate a CSRF token


const csrfToken = generateRandomToken();
app.get('/form', (req, res) => {
res.render('form', { csrfToken });
});

// Form with embedded CSRF token


<form method="POST" action="/submit">
<input type="hidden" name="csrf_token" value="<%= csrfToken %>">
<button type="submit">Submit</button>
</form>

Tokens are checked on the server before processing, ensuring requests are authentic.

9. What is OpenID Connect, and how does it complement OAuth?

Answer:
OpenID Connect (OIDC) is an identity layer built on top of OAuth 2.0. It enables secure user
authentication and identity verification. While OAuth focuses on authorization, OIDC
provides authentication services, returning an ID token that contains user information in a
JWT format.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
620

// Initiating an OIDC authentication request


const oidcUrl = "https://idp.com/auth";
window.location.href =
`${oidcUrl}?response_type=id_token&client_id=CLIENT_ID&scope=openid profile`;

// Extract the ID token from the response


const idToken = new URLSearchParams(window.location.hash).get('id_token');
console.log('ID Token:', idToken);

OIDC is widely used in social login systems like Google and Facebook.

10. What are some best practices for securing JavaScript applications?

Answer:
Best practices for securing JavaScript applications include:

1. Use HTTPS to encrypt data transmission.


2. Validate all inputs to prevent injection attacks.
3. Avoid eval() and other dangerous functions.
4. Use secure cookies with HttpOnly and Secure flags to protect session data.
5. Implement CSP to restrict the loading of untrusted resources.

For Example:

// Setting secure cookies in Express.js


app.use((req, res, next) => {
res.cookie('session', 'encrypted_value', { httpOnly: true, secure: true });
next();
});

Following these practices ensures your application remains resilient against common
vulnerabilities.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
621

11. What is the difference between stored XSS and reflected XSS?

Answer:
Stored XSS occurs when malicious scripts are saved on the server’s database or storage, such
as in comment sections, forums, or user profiles. Every time a user visits the affected page,
the script is executed in the victim's browser. This type of XSS is persistent and can affect
multiple users over time.

Reflected XSS is non-persistent and occurs when the server includes user-provided input
directly in its response. It typically happens when malicious code is embedded in a URL, and
the user is tricked into clicking it. The injected script is then executed in the user's browser
without being stored on the server.

For Example:

// Stored XSS Example: Malicious input stored in a comment field


const comment = "<script>alert('Stored XSS');</script>";
database.saveComment(comment); // Malicious script is stored

// Reflected XSS Example: URL-based attack


const userInput = new URLSearchParams(window.location.search).get('q');
document.getElementById("result").innerHTML = userInput; // Vulnerable!

// Safe way to avoid both types: Sanitize the output


document.getElementById("result").textContent = userInput;

Stored XSS is more dangerous because it affects all visitors to the page, while reflected XSS
only works if the attacker tricks the user into visiting a specially crafted URL.

12. What is DOM-based XSS, and how does it differ from other types of XSS?

Answer:
DOM-based XSS occurs entirely on the client side, where the malicious payload changes the
DOM structure using JavaScript. It does not rely on the server's response to deliver the
payload. Instead, it takes advantage of insecure DOM manipulations in the browser. This
makes DOM-based XSS harder to detect because it does not involve server interactions, and
the vulnerability exists only in the frontend code.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
622

Unlike stored or reflected XSS, where the server sends the malicious payload, DOM-based
XSS exploits client-side logic.

For Example:

// Vulnerable DOM-based XSS Example


const userInput = new URLSearchParams(window.location.search).get('name');
document.getElementById("greeting").innerHTML = `Hello, ${userInput}!`; //
Vulnerable

// Safe approach: Avoid using innerHTML directly


document.getElementById("greeting").textContent = `Hello, ${userInput}!`;

This example demonstrates how manipulating the DOM using innerHTML can open the door
for XSS attacks, while using textContent mitigates the risk.

13. How can content encoding help prevent XSS attacks?

Answer:
Content encoding ensures that user inputs are treated as plain text rather than executable
code. This encoding technique converts special HTML characters such as < and > into their
encoded equivalents (&lt; and &gt;). By encoding the content, browsers render the input as
text rather than interpreting it as code.

For Example:

// Encoding input to prevent XSS


function encodeHTML(str) {
const div = document.createElement("div");
div.textContent = str; // Converts the input to plain text
return div.innerHTML; // Encoded HTML output
}

// Usage
const userInput = "<script>alert('XSS');</script>";
document.getElementById("output").innerHTML = encodeHTML(userInput);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
623

By using content encoding, we ensure that even if a malicious script is entered, it will not
execute but instead be displayed as text.

14. How does using the eval() function introduce security risks?

Answer:
The eval() function evaluates a string as JavaScript code, making it very dangerous if used
improperly. If an attacker can inject code into the string passed to eval(), they can run
malicious scripts within the application's context, leading to severe security vulnerabilities,
such as stealing cookies, defacing the UI, or performing unintended actions.

For Example:

// Vulnerable code: User-controlled input passed to eval()


const userCode = prompt("Enter JavaScript code:");
eval(userCode); // Dangerous! Allows execution of any JavaScript

// Safe alternative: Use controlled functions or JSON parsing


const userInput = '{"name": "Alice"}';
const parsedData = JSON.parse(userInput); // Safe: Parsing JSON input
console.log(parsedData.name);

Avoid using eval() and opt for safer alternatives like JSON.parse() or template literals
whenever possible.

15. What is a Content Security Policy (CSP), and how does it help prevent
XSS?

Answer:
A Content Security Policy (CSP) is an HTTP response header that defines the allowed sources
for scripts, styles, images, and other resources on a web page. By limiting the origins from
which content can be loaded, CSP reduces the risk of XSS attacks by preventing the
execution of unauthorized scripts.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
624

For Example:

// Setting a CSP header in Express.js to block inline scripts


app.use((req, res, next) => {
res.setHeader("Content-Security-Policy", "script-src 'self'");
next();
});

In this example, only scripts from the same origin (self) are allowed, and any inline or
external scripts from other sources will be blocked.

16. What are secure cookies, and how do they improve web security?

Answer:
A secure cookie is transmitted only over HTTPS, ensuring that sensitive data, such as session
tokens, is encrypted in transit. Additionally, setting the HttpOnly flag prevents the cookie
from being accessed via JavaScript, mitigating the risk of XSS attacks.

For Example:

// Setting secure cookies with Express.js


app.use((req, res, next) => {
res.cookie('session', 'encrypted_value', {
httpOnly: true,
secure: true,
sameSite: 'strict'
});
next();
});

This configuration ensures that the cookie can only be sent over HTTPS and is inaccessible via
JavaScript, providing enhanced security.

17. How does the SameSite attribute help prevent CSRF attacks?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
625

Answer:
The SameSite cookie attribute prevents cookies from being sent with cross-site requests.
This reduces the risk of CSRF attacks by ensuring that cookies are only sent with requests
originating from the same origin.

The Lax setting allows cookies with top-level navigations (e.g., clicking a link), while Strict
ensures cookies are sent only with requests from the same site, providing maximum security.

For Example:

// Setting the SameSite attribute for cookies


app.use((req, res, next) => {
res.cookie('session', 'encrypted_value', { sameSite: 'strict' });
next();
});

With SameSite set to strict, the cookie will not be sent if the request originates from
another site, preventing CSRF attacks.

18. How does token-based authentication work, and how is it different from
session-based authentication?

Answer:
In token-based authentication, a server issues a token (e.g., JWT) to the client after
successful login. The client stores the token and sends it with each subsequent request in the
Authorization header. Since the server does not store session data, this approach is stateless.

Session-based authentication stores session data on the server, requiring the server to
maintain session state. Token-based authentication scales better for APIs and microservices
because it offloads state management to the client.

For Example:

// Token-based authentication flow


const token = localStorage.getItem('authToken');
fetch('/api/data', {
headers: { Authorization: `Bearer ${token}` }

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
626

});

19. What is the difference between OAuth and OpenID Connect?

Answer:
OAuth 2.0 is primarily an authorization protocol that allows applications to access resources
on behalf of a user without sharing their credentials. For example, OAuth enables a third-
party app to access your Google Calendar data with your permission.

OpenID Connect (OIDC) extends OAuth 2.0 to provide authentication services, allowing the
client to verify the user's identity and retrieve profile information in the form of a JWT token.

For Example:

// OAuth request for authorization


const oauthUrl = "https://auth-server.com/authorize";
window.location.href = `${oauthUrl}?response_type=token&client_id=CLIENT_ID`;

// OpenID Connect authentication request


const oidcUrl = "https://idp.com/auth";
window.location.href =
`${oidcUrl}?response_type=id_token&client_id=CLIENT_ID&scope=openid profile`;

20. What are JSON Web Tokens (JWTs) and how do they enhance security?

Answer:
JWTs (JSON Web Tokens) are compact, self-contained tokens used to securely transmit
information between two parties. JWTs consist of three parts: header, payload, and
signature. The payload contains claims about the user, while the signature ensures the
integrity of the token.

JWTs are commonly used for stateless authentication, meaning the server does not need to
store session data. Instead, the client sends the token with each request.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
627

// Creating a JWT token


const jwt = require('jsonwebtoken');
const token = jwt.sign({ userId: 123 }, 'secretKey', { expiresIn: '1h' });

// Verifying the token


jwt.verify(token, 'secretKey', (err, decoded) => {
if (err) {
console.log('Invalid token');
} else {
console.log('Decoded data:', decoded);
}
});

JWTs ensure each request is validated, enhancing security while minimizing server-side
storage.

21. How can an attacker exploit JSONP for XSS, and how can you mitigate
it?

Answer:
JSONP (JSON with Padding) is a technique used to load cross-domain data by embedding it
in a <script> tag. It allows websites to bypass the same-origin policy, which restricts how
scripts from one origin can interact with resources from another. However, JSONP can
introduce vulnerabilities if the server does not properly control or sanitize the callback
function provided in the request.

Exploitation:
Attackers can modify the callback parameter in the URL to inject malicious scripts. When
the browser loads the <script> tag with this payload, the malicious code runs in the context
of the vulnerable site.

For Example:

<!-- Malicious JSONP request -->


<script src="https://vulnerable-site.com/data?callback=alert"></script>

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
628

If the server responds:

alert({"data":"sensitive data"});

The browser executes the alert() function, showing that an attacker could run arbitrary
code.

Mitigation Strategies:

● Avoid using JSONP and adopt CORS policies for cross-origin requests.
● If JSONP must be used, validate the callback function name against a whitelist.
● Limit the domains allowed to access sensitive endpoints.

22. How does OAuth 2.0 handle token expiration and refresh tokens?

Answer:
OAuth 2.0 issues access tokens that have a short lifespan to reduce the risk of token leakage.
When an access token expires, the client application uses a refresh token to request a new
access token without requiring the user to log in again.

Access Tokens: Short-lived tokens that grant access to resources.


Refresh Tokens: Long-lived tokens that are used to obtain new access tokens.

For Example:

// Refresh token request


fetch('https://auth-server.com/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: 'your_refresh_token',
client_id: 'CLIENT_ID',
client_secret: 'CLIENT_SECRET'
})
}).then(response => response.json())

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
629

.then(data => console.log('New access token:', data.access_token));

The refresh token allows the user to stay authenticated without re-entering credentials,
enhancing user experience while maintaining security.

23. What is clickjacking, and how can JavaScript prevent it?

Answer:
Clickjacking is an attack where a malicious website overlays or embeds another site (e.g., a
banking portal) in an iframe. The victim interacts with the embedded site unknowingly,
leading to unintended actions such as money transfers or changing account settings.

Mitigation:

1. X-Frame-Options: Prevents your site from being embedded in iframes.


2. Content Security Policy (CSP): The frame-ancestors directive restricts which
domains can embed your site.

For Example:

// Setting X-Frame-Options in Express.js


app.use((req, res, next) => {
res.setHeader('X-Frame-Options', 'DENY'); // Prevents iframe embedding
next();
});

// CSP approach to prevent clickjacking


app.use((req, res, next) => {
res.setHeader('Content-Security-Policy', "frame-ancestors 'none'");
next();
});

Using both X-Frame-Options and CSP provides a robust defense against clickjacking.

24. How does JavaScript protect against timing attacks?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
630

Answer:
Timing attacks exploit differences in the time taken to perform operations. For example, if a
system compares passwords character-by-character and returns results faster for correct
characters, an attacker can infer the correct password by measuring response times.

Mitigation:
Implement constant-time comparison to ensure that the operation takes the same time
regardless of the input values.

For Example:

// Constant-time string comparison


function safeCompare(a, b) {
if (a.length !== b.length) return false;
let result = 0;
for (let i = 0; i < a.length; i++) {
result |= a.charCodeAt(i) ^ b.charCodeAt(i); // XOR comparison
}
return result === 0;
}
console.log(safeCompare('password', 'password')); // true

In this example, even if some characters match, the comparison takes the same time,
mitigating timing attacks.

25. What is Subresource Integrity (SRI), and how does it protect against
compromised scripts?

Answer:
Subresource Integrity (SRI) ensures that resources loaded from external sources (e.g., CDNs)
have not been tampered with. SRI works by including a cryptographic hash of the resource
in the <script> tag. If the content of the resource changes, the browser will block the script.

For Example:

<!-- SRI-enabled script -->


<script src="https://cdn.example.com/library.js"

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
631

integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxvT9dRkIY="
crossorigin="anonymous"></script>

SRI ensures that only the expected version of the resource is loaded, protecting users from
compromised third-party libraries.

26. How can JavaScript handle API rate limiting securely?

Answer:
API rate limiting prevents abuse by restricting the number of requests a client can make
within a certain time. If the limit is reached, the server responds with a 429 Too Many
Requests status and may include a Retry-After header indicating when the client can try
again.

For Example:

// Fetch with rate limit handling and retry logic


async function fetchWithRetry(url, retries = 3) {
try {
const response = await fetch(url);
if (response.status === 429) { // Rate limit exceeded
const retryAfter = response.headers.get('Retry-After');
console.log(`Rate limit exceeded. Retrying in ${retryAfter} seconds.`);
setTimeout(() => fetchWithRetry(url, retries - 1), retryAfter * 1000);
} else {
const data = await response.json();
console.log(data);
}
} catch (error) {
if (retries > 0) fetchWithRetry(url, retries - 1);
}
}
fetchWithRetry('https://api.example.com/data');

This implementation ensures the application handles rate limits gracefully by retrying the
request after the recommended time.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
632

27. What is HTTP Strict Transport Security (HSTS), and why is it important?

Answer:
HSTS ensures that browsers always connect to a site over HTTPS, even if the user tries to
access it using HTTP. This prevents protocol downgrade attacks and man-in-the-middle
(MITM) attacks where attackers intercept unencrypted traffic.

For Example:

// Enabling HSTS in Express.js


app.use((req, res, next) => {
res.setHeader('Strict-Transport-Security', 'max-age=31536000;
includeSubDomains');
next();
});

The max-age attribute specifies how long (in seconds) the browser should enforce HTTPS, and
includeSubDomains ensures that all subdomains are also covered by HSTS.

28. How does Cross-Origin Resource Sharing (CORS) work, and how can it
be configured securely?

Answer:
CORS allows a server to specify which domains can access its resources. Without CORS,
browsers block cross-origin requests by default for security reasons. By configuring CORS
headers, the server can selectively allow or deny access to resources from specific origins.

For Example:

// Setting CORS headers in Express.js


const cors = require('cors');
const app = express();

app.use(cors({

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
633

origin: 'https://example.com', // Allow only a specific origin


methods: ['GET', 'POST'], // Allow only certain methods
credentials: true // Allow cookies with cross-origin requests
}));

This setup ensures that only trusted origins can access the server’s resources.

29. What is the difference between symmetric and asymmetric encryption


in JavaScript?

Answer:
In symmetric encryption, the same key is used for both encryption and decryption. It is fast
but requires secure key management. In asymmetric encryption, a public key encrypts the
data, and a private key decrypts it. Asymmetric encryption is slower but more secure for
exchanging keys.

For Example:

// Symmetric encryption example using Node.js crypto module


const crypto = require('crypto');
const algorithm = 'aes-256-cbc';
const key = crypto.randomBytes(32);
const iv = crypto.randomBytes(16);

function encrypt(text) {
const cipher = crypto.createCipheriv(algorithm, key, iv);
return cipher.update(text, 'utf8', 'hex') + cipher.final('hex');
}

console.log(encrypt('Sensitive data'));

30. How does JavaScript handle cryptographic hashing, and why is it


important?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
634

Answer:
Hashing is a one-way operation that converts data into a fixed-size hash value. It is primarily
used for storing passwords securely and verifying data integrity. JavaScript provides hashing
functions through the crypto module.

For Example:

// Hashing with Node.js crypto module


const crypto = require('crypto');
const hash = crypto.createHash('sha256').update('password123').digest('hex');
console.log('Hash:', hash);

Hashing ensures that even if the database is compromised, the original data (e.g., passwords)
cannot be easily recovered from the hashes.

31. How do CSRF attacks differ from XSS attacks, and how can both be
mitigated?

Answer:
CSRF (Cross-Site Request Forgery) and XSS (Cross-Site Scripting) differ in the way they
compromise a system:

● CSRF exploits a user’s authenticated session to perform unintended actions without


the user’s knowledge. The attacker tricks the user into making requests (like a money
transfer or changing profile details) using their existing session.
● XSS injects malicious scripts that run in the user’s browser, often with the intent of
stealing session cookies, personal information, or altering the behavior of the site.

Mitigation Techniques:

● CSRF Mitigation:
1. CSRF Tokens: Unique tokens embedded in forms and validated by the server
on submission.
2. SameSite Cookies: Restrict sending cookies only on same-origin requests.
3. Referer/Origin Header Validation: Ensure requests originate from trusted
domains.
● XSS Mitigation:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
635

1. Sanitize Input: Remove dangerous characters from input.


2. Content Security Policy (CSP): Block inline scripts and restrict resource
loading.
3. Avoid innerHTML: Use textContent instead.

32. How does JavaScript handle the security of localStorage and


sessionStorage?

Answer:
localStorage and sessionStorage are browser-based storage mechanisms that allow
developers to store key-value pairs. However, both are vulnerable to XSS attacks since
malicious scripts running on the page can access them.

● localStorage: Persistent storage that remains available even after the browser is
closed.
● sessionStorage: Data that is available only within the current session and is cleared
when the browser tab is closed.

Security Risks:

● XSS Vulnerability: Malicious scripts can access and exfiltrate stored tokens.
● Sensitive Data Exposure: Storing authentication tokens in localStorage or
sessionStorage may expose users to security risks.

Mitigation Strategies:

● HttpOnly Cookies: Use secure cookies for storing authentication tokens.


● Sanitize Input: Prevent XSS to protect stored data.
● Clear Storage on Logout: Ensure stored data is cleared when the user logs out.

For Example:

// Setting sessionStorage
sessionStorage.setItem('user', JSON.stringify({ username: 'Alice' }));
// Retrieving sessionStorage
const user = JSON.parse(sessionStorage.getItem('user'));
console.log(user.username); // "Alice"

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
636

33. What is key stretching, and why is it important for password hashing?

Answer:
Key stretching strengthens passwords by applying a hashing function multiple times to slow
down brute-force attempts. Without key stretching, weak passwords could be cracked
quickly by attackers using powerful hardware. Algorithms like PBKDF2, bcrypt, and argon2
implement key stretching.

How It Works:

1. A password is combined with a salt to prevent identical passwords from producing


identical hashes.
2. The hash function is applied repeatedly to slow down the computation process,
making brute-force attacks more time-consuming.

For Example:

const crypto = require('crypto');

// PBKDF2 for password hashing with key stretching


const hashPassword = (password, salt) =>
crypto.pbkdf2Sync(password, salt, 100000, 64, 'sha512').toString('hex');

const salt = crypto.randomBytes(16).toString('hex');


const hashedPassword = hashPassword('securepassword', salt);
console.log('Hashed Password:', hashedPassword);

34. How does rate-limiting prevent brute-force attacks on authentication


endpoints?

Answer:
Rate-limiting controls how frequently a user or an IP can attempt login within a given
timeframe. This is crucial for preventing brute-force attacks, where attackers attempt to
guess passwords by rapidly submitting login requests.

How Rate-Limiting Works:

● After a certain number of failed attempts, the server temporarily blocks further
attempts.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
637

● The server may respond with a 429 Too Many Requests status or implement a
backoff strategy.

For Example:

const rateLimit = require('express-rate-limit');

// Apply rate limit to login endpoint


const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // Limit each IP to 5 requests per 15 minutes
message: 'Too many login attempts. Try again later.',
});

app.post('/login', loginLimiter, (req, res) => {


res.send('Login endpoint');
});

35. How can you securely store JWTs in a client-side JavaScript application?

Answer:
JWTs (JSON Web Tokens) are used to authenticate users and authorize access to resources.
Storing JWTs insecurely can expose users to XSS attacks, where attackers can steal tokens
and hijack user sessions.

Best Practices for Storing JWTs:

1. HttpOnly Cookies: Store JWTs in cookies that cannot be accessed via JavaScript.
2. Secure and SameSite Cookies: Prevent cross-site usage and transmission over non-
HTTPS connections.
3. Token Rotation: Issue new tokens frequently to minimize the risk of token theft.

For Example:

// Storing a JWT in a Secure, HttpOnly Cookie


app.use((req, res, next) => {
res.cookie('authToken', 'JWT_VALUE', {
httpOnly: true,

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
638

secure: true,
sameSite: 'strict'
});
next();
});

36. What is the difference between encryption and hashing in JavaScript?

Answer:

● Encryption: A reversible process that converts plaintext into ciphertext using a key.
With the correct key, the data can be decrypted back to its original form. Encryption
ensures data confidentiality.
● Hashing: A one-way operation that converts data into a fixed-length hash. Hashes are
used to verify data integrity and securely store passwords, as they cannot be reversed.

For Example (Encryption):

const crypto = require('crypto');


const key = crypto.randomBytes(32);
const iv = crypto.randomBytes(16);

function encrypt(text) {
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
return cipher.update(text, 'utf8', 'hex') + cipher.final('hex');
}

console.log(encrypt('Sensitive Data'));

For Example (Hashing):

const hash = crypto.createHash('sha256').update('password123').digest('hex');


console.log('Hashed Value:', hash);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
639

37. How can you secure a WebSocket connection in JavaScript?

Answer:
WebSockets provide a persistent, two-way communication channel between a client and
server. However, WebSocket connections can be vulnerable to man-in-the-middle (MITM)
attacks and cross-site WebSocket hijacking.

Mitigation Strategies:

1. Use wss://: Encrypt the WebSocket connection using TLS.


2. Authenticate Connections: Use a token-based system for authentication.
3. Validate Origins: Allow connections only from trusted domains.

For Example:

const WebSocket = require('ws');


const server = new WebSocket.Server({ port: 8080 });

server.on('connection', (socket, req) => {


const token = req.headers['sec-websocket-protocol'];
if (validateToken(token)) {
socket.send('Connected securely');
} else {
socket.close();
}
});

38. What is HTTP/2, and how does it improve security and performance?

Answer:
HTTP/2 improves upon HTTP/1.1 by introducing features like multiplexing, header
compression, and server push, all of which enhance performance. HTTP/2 also requires TLS
encryption, which makes it more secure by default.

Benefits:

● Multiplexing: Multiple requests are sent over a single connection.


● Header Compression: Reduces the size of HTTP headers, improving performance.
● Mandatory Encryption: Only supports HTTPS connections, providing better security.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
640

39. How do CSP violations get reported, and how can you use them for
security monitoring?

Answer:
A Content Security Policy (CSP) can be configured to report violations to a monitoring
endpoint. This allows administrators to detect unauthorized scripts and assess potential XSS
vulnerabilities.

For Example:

// Setting up CSP with reporting in Express.js


app.use((req, res, next) => {
res.setHeader('Content-Security-Policy',
"default-src 'self'; report-uri /csp-violation");
next();
});

// Endpoint to receive CSP violation reports


app.post('/csp-violation', (req, res) => {
console.log('CSP Violation:', req.body);
res.sendStatus(204);
});

40. How can JavaScript handle multi-factor authentication (MFA) securely?

Answer:
MFA (Multi-Factor Authentication) adds an extra layer of security by requiring users to
provide an additional verification factor, such as a one-time password (OTP) or a push
notification from an authenticator app.

For Example:

// Sending an OTP for MFA


const sendOTP = async (phoneNumber) => {
const otp = generateOTP(); // Generate a 6-digit OTP
await sendSms(phoneNumber, `Your OTP is ${otp}`);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
641

};

// Verifying the OTP


const verifyOTP = (inputOtp, storedOtp) => inputOtp === storedOtp;

MFA ensures that even if a password is compromised, the attacker cannot log in without the
additional factor.

SCENARIO QUESTIONS
41. Scenario: A user submits a malicious script in a comment box on a social
media platform, which executes when other users view the comment.

Question: How can the platform prevent such Cross-Site Scripting (XSS) attacks?

Answer:
This is a Stored XSS attack. The attacker injects malicious scripts into user-generated content
stored on the server. When another user loads the page, the script runs in the context of the
victim’s browser, potentially stealing session cookies or personal information.

To mitigate this:

1. Sanitize user inputs by removing or escaping dangerous characters like <, >, and "
from user submissions.
2. Use content encoding to convert HTML-sensitive characters into safe text.
3. Implement Content Security Policy (CSP) to restrict resource loading.

For Example:

// Express.js: Sanitizing input to prevent XSS


const sanitizeHtml = require('sanitize-');

app.post('/comment', (req, res) => {


const sanitizedComment = sanitizeHtml(req.body.comment);
database.saveComment(sanitizedComment);
res.send('Comment posted');
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
642

By sanitizing the input, the platform ensures that scripts embedded within comments are
rendered as plain text.

42. Scenario: An attacker sends a crafted link to a user, which, when


clicked, results in an unwanted action on the user’s account.

Question: How can a web application protect against CSRF attacks?

Answer:
This is a CSRF attack, where the attacker leverages the user’s authenticated session to
perform unauthorized actions. This usually happens when session cookies are sent with all
requests.

To prevent CSRF:

1. Use CSRF tokens to validate that the request is genuine.


2. Implement SameSite cookies to prevent cookies from being sent with cross-site
requests.
3. Verify the Origin and Referer headers to confirm that requests originate from trusted
sources.

For Example:

// Adding a CSRF token to a form


app.get('/form', (req, res) => {
const csrfToken = generateCsrfToken();
res.render('form', { csrfToken });
});

// Validating the token on form submission


app.post('/submit', (req, res) => {
if (req.body.csrfToken === req.session.csrfToken) {
res.send('Action successful');
} else {
res.status(403).send('CSRF validation failed');
}
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
643

43. Scenario: An attacker inputs SQL queries into a search field to extract
sensitive data from the database.

Question: How can SQL Injection be prevented in JavaScript applications?

Answer:
SQL Injection occurs when untrusted user inputs are directly embedded into SQL queries,
allowing attackers to manipulate the database. This can lead to unauthorized data access or
deletion.

To prevent SQL Injection:

1. Use parameterized queries or prepared statements to treat inputs as data rather


than code.
2. Validate and sanitize all user inputs before executing queries.
3. Avoid dynamically constructing SQL queries with user inputs.

For Example:

// Using parameterized queries with MySQL in Node.js


const mysql = require('mysql2');
const connection = mysql.createConnection({ host, user, database });

const searchUsers = (username) => {


const query = 'SELECT * FROM users WHERE username = ?';
connection.execute(query, [username], (err, results) => {
if (err) throw err;
console.log(results);
});
};

44. Scenario: A developer needs to ensure that only authenticated users


can access a specific API endpoint.

Question: How can JWT (JSON Web Token) be used to secure API endpoints?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
644

Answer:
JWT is commonly used to authenticate users and authorize access to resources. After login,
the server issues a JWT, which the client stores and sends with subsequent requests.

To secure API endpoints:

1. The server validates the JWT before granting access.


2. Tokens should have a limited lifespan to reduce the risk of misuse.
3. Store JWTs securely, preferably in HttpOnly cookies, to prevent XSS attacks.

For Example:

const jwt = require('jsonwebtoken');

// Generating a JWT token


const generateToken = (user) => {
return jwt.sign({ id: user.id }, 'secretKey', { expiresIn: '1h' });
};

// Verifying the token in an API endpoint


app.get('/protected', (req, res) => {
const token = req.headers.authorization.split(' ')[1];
jwt.verify(token, 'secretKey', (err, decoded) => {
if (err) return res.status(401).send('Unauthorized');
res.send('Welcome to the protected route');
});
});

45. Scenario: A client application needs to access user resources from a


third-party service without exposing user credentials.

Question: How can OAuth 2.0 be implemented for secure authorization?

Answer:
OAuth 2.0 is an authorization framework that allows a client to access user resources on
another service without sharing credentials. It works by issuing access tokens.

Steps:

1. The user grants permission to the client.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
645

2. The authorization server issues an access token.


3. The client uses the token to access resources on behalf of the user.

For Example:

// OAuth 2.0 authorization request


const authUrl = 'https://auth-server.com/authorize';
window.location.href =
`${authUrl}?response_type=token&client_id=CLIENT_ID&redirect_uri=https://client-
app.com/callback`;

// Handling the token after redirection


const token = new URLSearchParams(window.location.hash).get('access_token');
console.log('Access Token:', token);

46. Scenario: A web application needs to verify a user's identity and retrieve
profile information from a third-party service.

Question: How can OpenID Connect (OIDC) be used to achieve this?

Answer:
OpenID Connect (OIDC) is an authentication layer on top of OAuth 2.0. It allows clients to
verify a user’s identity by receiving an ID token in addition to the access token.

Steps:

1. The client redirects the user to the authorization server.


2. The authorization server returns an ID token containing user information.
3. The client verifies the ID token to authenticate the user.

For Example:

// OpenID Connect authentication request


const oidcUrl = 'https://idp.com/auth';
window.location.href =
`${oidcUrl}?response_type=id_token&client_id=CLIENT_ID&scope=openid
profile&redirect_uri=https://client.com/callback`;

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
646

// Extracting and decoding the ID token


const idToken = new URLSearchParams(window.location.hash).get('id_token');
console.log('ID Token:', idToken);

47. Scenario: A developer must restrict which external domains can access
their API to prevent unauthorized cross-origin requests.

Question: How can CORS (Cross-Origin Resource Sharing) be configured for


security?

Answer:
CORS allows servers to specify which origins can access their resources. Without CORS,
browsers block cross-origin requests by default.

To securely configure CORS:

1. Whitelist specific origins that are allowed to access the API.


2. Restrict methods and headers that can be used.
3. Use credentials cautiously to avoid exposing sensitive data.

For Example:

const cors = require('cors');


app.use(cors({
origin: 'https://trusted-site.com',
methods: ['GET', 'POST'],
credentials: true
}));

48. Scenario: A banking application wants to ensure that only secure


connections are used for transmitting sensitive information.

Question: How can HTTP Strict Transport Security (HSTS) be implemented?

Answer:
HSTS ensures that browsers only connect to the server over HTTPS, even if the user types

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
647

http:// in the address bar. This prevents man-in-the-middle (MITM) attacks and protocol
downgrades.

For Example:

// Enabling HSTS in Express.js


app.use((req, res, next) => {
res.setHeader('Strict-Transport-Security', 'max-age=31536000;
includeSubDomains');
next();
});

49. Scenario: A developer needs to monitor unauthorized resource usage


on a web application.

Question: How can CSP (Content Security Policy) reports help detect potential
attacks?

Answer:
CSP can be configured to send violation reports whenever unauthorized scripts or resources
are blocked. This helps in detecting XSS attacks and other suspicious activities.

For Example:

// Setting up CSP with reporting in Express.js


app.use((req, res, next) => {
res.setHeader('Content-Security-Policy', "default-src 'self'; report-uri /csp-
report");
next();
});

// Handling CSP violation reports


app.post('/csp-report', (req, res) => {
console.log('CSP Violation:', req.body);
res.sendStatus(204);
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
648

50. Scenario: A social media application wants to detect brute-force login


attempts.

Question: How can rate-limiting be implemented to prevent such attacks?

Answer:
Rate-limiting helps prevent brute-force attacks by restricting the number of login attempts
allowed from a single IP or user within a specific time window.

For Example:

const rateLimit = require('express-rate-limit');

const loginLimiter = rateLimit({


windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 attempts per IP
message: 'Too many login attempts. Try again later.',
});

app.post('/login', loginLimiter, (req, res) => {


res.send('Login attempt');
});

51. Scenario: A user enters input into a search field, and the application
dynamically updates the page content using innerHTML.

Question: How can this approach lead to a security issue, and what are safer
alternatives?

Answer:
Using innerHTML can introduce DOM-based XSS if user input is directly injected into the
DOM without validation. If an attacker provides a malicious script as input, the script will
execute in the user's browser, potentially stealing sensitive data or hijacking the session.

Safer Alternatives:

1. Use textContent or createTextNode to insert text safely.


2. Use libraries like DOMPurify to sanitize HTML content if needed.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
649

For Example:

// Vulnerable code using innerHTML


document.getElementById('result').innerHTML = userInput; // Dangerous!

// Safe approach using textContent


document.getElementById('result').textContent = userInput;

52. Scenario: A JavaScript-based application needs to communicate with a


backend API and handle user tokens.

Question: What are the risks of storing JWTs in localStorage or sessionStorage?

Answer:
Storing JWTs in localStorage or sessionStorage exposes them to XSS attacks since
malicious scripts running on the site can access these storage mechanisms. If a token is
stolen, an attacker can use it to impersonate the user.

Best Practices:

1. Store JWTs in HttpOnly cookies to prevent JavaScript access.


2. Use token rotation to issue new tokens frequently.
3. Implement CSP to reduce the risk of XSS attacks.

For Example:

// Setting an HttpOnly cookie for secure storage


app.use((req, res, next) => {
res.cookie('authToken', 'JWT_VALUE', { httpOnly: true, secure: true });
next();
});

53. Scenario: A developer needs to validate user input before inserting it


into the database.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
650

Question: How can improper input validation lead to security vulnerabilities?

Answer:
Improper input validation can lead to security issues like SQL Injection, where attackers can
manipulate database queries. This occurs when input is directly embedded into SQL queries
without sanitization, allowing attackers to gain unauthorized access to the database.

Solution: Use parameterized queries or ORMs to ensure that inputs are treated as data, not
code.

For Example:

const mysql = require('mysql2');


const query = 'SELECT * FROM users WHERE username = ?';
connection.execute(query, [userInput], (err, results) => {
if (err) throw err;
console.log(results);
});

54. Scenario: A web application handles sensitive user data and needs
secure communication.

Question: Why is HTTPS important, and how does it protect data?

Answer:
HTTPS encrypts data transmitted between the client and server, preventing man-in-the-
middle (MITM) attacks. Without HTTPS, attackers can intercept data, including passwords
and personal information, sent over the network.

For Example:

// Setting up an HTTPS server with Node.js


const https = require('https');
const fs = require('fs');
const options = {
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-cert.pem')
};

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
651

https.createServer(options, (req, res) => {


res.writeHead(200);
res.end('Secure communication established!');
}).listen(443);

55. Scenario: A developer needs to ensure that only authorized users can
access specific routes in an API.

Question: How can middleware be used for authentication in a JavaScript


backend?

Answer:
Middleware can intercept requests and verify JWTs or other authentication tokens before
allowing access to protected routes. If the token is missing or invalid, the middleware can
block the request.

For Example:

// Middleware to verify JWTs


const jwt = require('jsonwebtoken');

const authenticate = (req, res, next) => {


const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.status(401).send('Token missing');

jwt.verify(token, 'secretKey', (err, decoded) => {


if (err) return res.status(403).send('Invalid token');
req.user = decoded;
next();
});
};

app.get('/protected', authenticate, (req, res) => {


res.send('Access granted to protected route');
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
652

56. Scenario: A web app allows users to upload files.

Question: What security risks are involved, and how can they be mitigated?

Answer:
Allowing users to upload files can expose the application to malware and script injection
attacks if the files are not properly validated.

Mitigation:

1. Restrict file types to only those required (e.g., images).


2. Validate file size and content.
3. Store uploaded files outside the web root to prevent direct access.

For Example:

const multer = require('multer');


const upload = multer({ dest: 'uploads/', limits: { fileSize: 1 * 1024 * 1024 } });
// 1MB

app.post('/upload', upload.single('file'), (req, res) => {


if (!req.file) return res.status(400).send('Invalid file');
res.send('File uploaded successfully');
});

57. Scenario: A user logs out of an application but their session cookie
remains valid.

Question: How can sessions be invalidated securely on logout?

Answer:
To ensure security, session cookies must be invalidated upon logout to prevent session
hijacking. This can be achieved by deleting the session data on the server and clearing the
cookie.

For Example:

app.post('/logout', (req, res) => {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
653

req.session.destroy(); // Destroy session on the server


res.clearCookie('session'); // Clear the session cookie
res.send('Logged out successfully');
});

58. Scenario: A developer needs to ensure that cookies are only sent over
secure connections.

Question: How can secure cookies be implemented?

Answer:
Secure cookies are transmitted only over HTTPS connections, ensuring that session data is
not exposed to attackers monitoring the network.

For Example:

app.use((req, res, next) => {


res.cookie('session', 'encrypted_value', {
httpOnly: true,
secure: true,
sameSite: 'strict'
});
next();
});

59. Scenario: An attacker tries to overload the API with a high volume of
requests.

Question: How can rate limiting help prevent such attacks?

Answer:
Rate limiting controls how many requests a client can make in a given period, protecting
against DDoS attacks and abuse. It temporarily blocks clients that exceed the allowed
threshold.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
654

const rateLimit = require('express-rate-limit');

const apiLimiter = rateLimit({


windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests
message: 'Too many requests, please try again later.',
});

app.use('/api/', apiLimiter);

60. Scenario: A developer needs to detect and report security policy


violations in real time.

Question: How can CSP (Content Security Policy) reports be used for monitoring?

Answer:
A Content Security Policy (CSP) can be configured to send reports whenever unauthorized
scripts or resources are blocked. This helps detect XSS attacks and other violations early.

For Example:

// Setting up CSP with reporting


app.use((req, res, next) => {
res.setHeader('Content-Security-Policy',
"default-src 'self'; report-uri /csp-report");
next();
});

// Handling CSP violation reports


app.post('/csp-report', (req, res) => {
console.log('CSP Violation:', req.body);
res.sendStatus(204);
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
655

61. Scenario: A user logs into a single-page application (SPA) and the
developer needs to ensure that authentication persists across page reloads
without compromising security.

Question: What are the best practices for securely storing authentication tokens
in a SPA?

Answer:
In SPAs, tokens can be stored either in localStorage, sessionStorage, or HttpOnly cookies.
However, storing them in localStorage or sessionStorage is risky due to XSS
vulnerabilities. Using HttpOnly cookies is the safest way since they are inaccessible to
JavaScript.

Best Practices:

1. HttpOnly Cookies: Store tokens securely to prevent XSS attacks.


2. Token Rotation: Refresh tokens periodically to limit exposure.
3. CSP Implementation: Mitigate XSS risks by restricting inline scripts.
4. Logout Handling: Clear tokens from both storage and the server upon logout.

For Example:

// Setting a Secure, HttpOnly cookie for JWT


app.use((req, res, next) => {
res.cookie('authToken', 'JWT_VALUE', {
httpOnly: true,
secure: true,
sameSite: 'strict'
});
next();
});

62. Scenario: An application processes sensitive user data, and the


developer needs to ensure it is encrypted both in transit and at rest.

Question: How can JavaScript ensure data is securely encrypted before storage?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
656

Answer:
To ensure data security:

1. Encrypt data at rest: Use AES or RSA encryption before storing it in a database or
local file.
2. Encrypt data in transit: Use HTTPS for communication between the client and server.
3. Use TLS: Establish encrypted connections to protect against eavesdropping.

For Example:

const crypto = require('crypto');

// AES-256 encryption
const key = crypto.randomBytes(32);
const iv = crypto.randomBytes(16);

function encrypt(text) {
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
return cipher.update(text, 'utf8', 'hex') + cipher.final('hex');
}

console.log(encrypt('Sensitive Data'));

63. Scenario: A client application accesses multiple services using OAuth


tokens.

Question: How can the application securely handle token expiration and refresh
tokens?

Answer:
Access tokens typically have short lifespans to mitigate misuse. Refresh tokens allow
applications to obtain new access tokens without requiring user re-authentication. However,
refresh tokens must be stored securely.

Best Practices:

1. Store refresh tokens in HttpOnly cookies to prevent XSS.


2. Implement token rotation to reduce the window of token misuse.
3. Use short-lived access tokens with frequent refreshes.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
657

For Example:

// Requesting a new access token with a refresh token


fetch('https://auth-server.com/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: 'REFRESH_TOKEN',
client_id: 'CLIENT_ID',
client_secret: 'CLIENT_SECRET'
})
}).then(response => response.json())
.then(data => console.log('New Access Token:', data.access_token));

64. Scenario: A web application handles large amounts of traffic, and the
developer needs to prevent session fixation attacks.

Question: What is session fixation, and how can it be mitigated?

Answer:
Session fixation occurs when an attacker forces a user to log in with a known session ID.
Once the user logs in, the attacker uses the same session to impersonate them.

Mitigation Strategies:

1. Regenerate session IDs upon login.


2. Use Secure, HttpOnly cookies to store session IDs.
3. Set session expiration times to limit the attack window.

For Example:

// Regenerating session ID on login


app.post('/login', (req, res) => {
req.session.regenerate((err) => {
if (err) return res.status(500).send('Session regeneration failed');
res.send('Session ID regenerated successfully');
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
658

});

65. Scenario: A developer wants to prevent brute-force attacks on a login


endpoint.

Question: How can exponential backoff be implemented to prevent brute-force


attempts?

Answer:
Exponential backoff increases the delay between login attempts after each failed attempt,
reducing the effectiveness of brute-force attacks. With each failure, the wait time doubles.

For Example:

let attempts = 0;

app.post('/login', (req, res) => {


const waitTime = Math.pow(2, attempts) * 1000; // Exponential backoff

setTimeout(() => {
const isAuthenticated = authenticate(req.body);
if (isAuthenticated) {
attempts = 0;
res.send('Login successful');
} else {
attempts++;
res.status(401).send('Login failed');
}
}, waitTime);
});

66. Scenario: A developer needs to prevent clickjacking attacks on their


site.

Question: How can the application ensure that it is not embedded in iframes?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
659

Answer:
Clickjacking occurs when an attacker embeds a legitimate site in an invisible iframe to trick
users into performing unintended actions.

Mitigation:

1. Use the X-Frame-Options header to prevent embedding.


2. Implement Content Security Policy (CSP) with frame-ancestors.

For Example:

// Preventing clickjacking with X-Frame-Options


app.use((req, res, next) => {
res.setHeader('X-Frame-Options', 'DENY');
next();
});

// CSP approach
app.use((req, res, next) => {
res.setHeader('Content-Security-Policy', "frame-ancestors 'none'");
next();
});

67. Scenario: A developer needs to detect unauthorized resource access


attempts.

Question: How can audit logging be implemented to track and respond to


security incidents?

Answer:
Audit logs provide a record of security-related events, such as login attempts and
unauthorized resource access. Proper logging helps in incident detection and response.

For Example:

const fs = require('fs');

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
660

// Log failed login attempts


app.post('/login', (req, res) => {
const isAuthenticated = authenticate(req.body);
if (!isAuthenticated) {
const logEntry = `Failed login attempt: ${new Date().toISOString()}\n`;
fs.appendFile('audit.log', logEntry, (err) => {
if (err) console.error('Failed to log event');
});
return res.status(401).send('Login failed');
}
res.send('Login successful');
});

68. Scenario: A developer needs to enforce strong passwords across the


user base.

Question: How can JavaScript enforce password strength policies?

Answer:
A strong password policy requires users to create passwords with a mix of uppercase and
lowercase letters, numbers, and special characters. It can be enforced using regular
expressions.

For Example:

const passwordPolicy = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-


z\d@$!%*?&]{8,}$/;

const isValidPassword = (password) => passwordPolicy.test(password);

console.log(isValidPassword('StrongP@ssw0rd')); // true
console.log(isValidPassword('weakpassword')); // false

69. Scenario: A user needs to authenticate using two-factor authentication


(2FA).

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
661

Question: How can 2FA be implemented with JavaScript?

Answer:
2FA requires a second factor, such as an OTP (One-Time Password) sent via SMS, in addition
to the user’s password.

For Example:

const generateOTP = () => Math.floor(100000 + Math.random() * 900000).toString();

app.post('/send-otp', (req, res) => {


const otp = generateOTP();
// Simulate sending OTP via SMS
console.log(`OTP for ${req.body.phoneNumber}: ${otp}`);
res.send('OTP sent');
});

70. Scenario: A developer needs to minimize the impact of token theft.

Question: How can token rotation improve security?

Answer:
Token rotation ensures that tokens are frequently replaced with new ones, reducing the risk
of token misuse if a token is stolen. When a new token is issued, the old token becomes
invalid.

For Example:

let refreshTokens = [];

app.post('/refresh-token', (req, res) => {


const oldToken = req.body.token;
if (!refreshTokens.includes(oldToken)) return res.status(403).send('Invalid
token');

const newToken = generateNewToken(); // Generate a new token


refreshTokens = refreshTokens.filter(token => token !== oldToken);
refreshTokens.push(newToken);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
662

res.send({ token: newToken });


});

71. Scenario: A developer wants to prevent Cross-Origin Resource Sharing


(CORS) vulnerabilities in a JavaScript API.

Question: How can CORS be securely implemented to control cross-origin


requests?

Answer:
CORS (Cross-Origin Resource Sharing) allows a server to specify which domains can access
its resources. Misconfiguring CORS can expose APIs to unauthorized access, leading to
security risks.

Best Practices:

1. Whitelist trusted origins to limit access.


2. Restrict HTTP methods to only those necessary.
3. Validate headers and allow credentials only when necessary.

For Example:

const cors = require('cors');

const corsOptions = {
origin: 'https://trusted-site.com',
methods: ['GET', 'POST'], // Restrict methods
credentials: true, // Use only if necessary
};

app.use(cors(corsOptions));

This configuration allows only requests from a specific origin and limits the HTTP methods to
GET and POST.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
663

72. Scenario: A web application integrates with third-party services using


OAuth.

Question: How can a secure OAuth authorization flow be implemented?

Answer:
OAuth provides a way for users to grant third-party applications access to their resources
without sharing credentials. The Authorization Code Flow is the most secure OAuth flow for
server-side applications.

Steps:

1. Redirect the user to the authorization server to obtain an authorization code.


2. Exchange the authorization code for an access token.

For Example:

// Step 1: Redirect to the authorization server


const authUrl = 'https://auth-server.com/authorize';
window.location.href =
`${authUrl}?response_type=code&client_id=CLIENT_ID&redirect_uri=https://client-
app.com/callback`;

// Step 2: Exchange authorization code for access token


app.post('/callback', (req, res) => {
const code = req.query.code;
fetch('https://auth-server.com/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code: code,
redirect_uri: 'https://client-app.com/callback',
client_id: 'CLIENT_ID',
client_secret: 'CLIENT_SECRET',
}),
}).then(response => response.json())
.then(data => res.send(data));
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
664

73. Scenario: A user repeatedly tries to log in with invalid credentials.

Question: How can account lockout mechanisms prevent brute-force attacks?

Answer:
Account lockout temporarily disables an account after multiple failed login attempts,
mitigating brute-force attacks.

Implementation Steps:

1. Track failed login attempts.


2. Lock the account after a predefined number of failures.
3. Notify the user and allow unlocking via a recovery mechanism.

For Example:

const userAttempts = {};

app.post('/login', (req, res) => {


const { username } = req.body;

if (userAttempts[username] && userAttempts[username].attempts >= 5) {


return res.status(429).send('Account locked. Try again later.');
}

const isAuthenticated = authenticate(req.body);


if (!isAuthenticated) {
userAttempts[username] = userAttempts[username] || { attempts: 0 };
userAttempts[username].attempts++;
return res.status(401).send('Invalid credentials');
}

userAttempts[username].attempts = 0;
res.send('Login successful');
});

74. Scenario: A developer is concerned about securing API keys in frontend


code.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
665

Question: How can API keys be protected in JavaScript applications?

Answer:
Exposing API keys in frontend code can lead to unauthorized usage. To secure API keys:

1. Use backend proxy servers to make API calls instead of directly from the frontend.
2. Restrict API keys by origin, IP, or usage limits.
3. Rotate keys periodically to limit the damage if a key is compromised.

For Example:

// Backend proxy to protect API keys


app.get('/data', (req, res) => {
fetch('https://third-party-api.com/data', {
headers: { 'Authorization': `Bearer ${process.env.API_KEY}` },
})
.then(response => response.json())
.then(data => res.send(data));
});

75. Scenario: An attacker manipulates URLs to gain unauthorized access to


user data.

Question: How can access control be implemented to secure API endpoints?

Answer:
Access control ensures that users can only access resources they are authorized for. Role-
based access control (RBAC) is a common approach where users are assigned roles that
define their permissions.

For Example:

// Middleware for role-based access control


const authorize = (role) => (req, res, next) => {
if (req.user.role !== role) {
return res.status(403).send('Forbidden');
}
next();

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
666

};

app.get('/admin', authenticate, authorize('admin'), (req, res) => {


res.send('Welcome, admin');
});

Here, only users with the admin role can access the /admin route.

76. Scenario: A developer wants to validate incoming API requests to


prevent malicious inputs.

Question: How can input validation prevent security vulnerabilities?

Answer:
Input validation ensures that only expected data is accepted by the application, mitigating
attacks such as SQL Injection, XSS, and Command Injection.

Best Practices:

1. Whitelist valid input patterns using regular expressions.


2. Escape special characters in user inputs.
3. Limit input length to prevent buffer overflow attacks.

For Example:

const usernamePattern = /^[a-zA-Z0-9_]{3,15}$/;

app.post('/register', (req, res) => {


const { username } = req.body;
if (!usernamePattern.test(username)) {
return res.status(400).send('Invalid username');
}
res.send('User registered successfully');
});

77. Scenario: A developer wants to minimize the impact of token theft.


INTERVIEWNINJA.IN ECOMNOWVENTURESTM
667

Question: How can token expiration improve security?

Answer:
Short-lived tokens limit the time an attacker can misuse a stolen token. After expiration,
users must request a new token using a refresh token.

For Example:

// JWT with a short expiration time


const jwt = require('jsonwebtoken');

const generateToken = (user) => {


return jwt.sign({ id: user.id }, 'secretKey', { expiresIn: '15m' });
};

app.post('/login', (req, res) => {


const token = generateToken(req.body.user);
res.send({ token });
});

78. Scenario: A developer notices unusual activity on a user account.

Question: How can anomaly detection be implemented to identify suspicious


behavior?

Answer:
Anomaly detection identifies unusual behavior, such as sudden changes in login location or
high-frequency requests, which could indicate an attack.

For Example:

const anomalyThreshold = 10; // Example threshold

const monitorActivity = (userId, activity) => {


const activities = userActivities[userId] || [];
activities.push(activity);
if (activities.length > anomalyThreshold) {
alertAdmin(`Suspicious activity detected for user: ${userId}`);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
668

}
};

79. Scenario: A developer wants to secure WebSocket connections in their


application.

Question: How can WebSocket connections be protected against hijacking?

Answer:
WebSocket hijacking occurs when an attacker intercepts or takes over a WebSocket
connection.

Mitigation Strategies:

1. Use wss:// to encrypt WebSocket traffic.


2. Authenticate connections before allowing communication.
3. Validate origins to prevent unauthorized access.

For Example:

const WebSocket = require('ws');


const server = new WebSocket.Server({ port: 8080 });

server.on('connection', (socket, req) => {


const token = req.headers['sec-websocket-protocol'];
if (validateToken(token)) {
socket.send('Connection established');
} else {
socket.close();
}
});

80. Scenario: A user logs into multiple devices, and the developer needs to
manage sessions efficiently.

Question: How can session management be optimized across multiple devices?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
669

Answer:
Managing sessions across devices ensures consistent user experience and security. Token-
based sessions are recommended to handle multi-device login efficiently.

For Example:

const sessions = {};

app.post('/login', (req, res) => {


const token = generateToken(req.body.user);
sessions[req.body.user.id] = sessions[req.body.user.id] || [];
sessions[req.body.user.id].push(token);
res.send({ token });
});

// Invalidate session across all devices


app.post('/logout-all', (req, res) => {
sessions[req.user.id] = [];
res.send('Logged out from all devices');
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
670

Chapter 12: JavaScript in DevOps and Automation

THEORETICAL QUESTIONS
1. What is the role of JavaScript in DevOps?

Answer:
JavaScript plays a crucial role in DevOps, particularly through Node.js, as it allows developers
to create automation tools, manage configurations, and streamline CI/CD pipelines. With
JavaScript, teams can automate repetitive tasks, build monitoring systems, and handle
deployments efficiently.

JavaScript’s versatility in both front-end and back-end development extends to DevOps


practices, enabling seamless integration across projects. Additionally, tools like Webpack,
npm, and Yarn support build processes, dependency management, and packaging for
smoother development cycles.

For Example:
A Node.js script can automate the deployment of an application by triggering shell
commands or interacting with APIs to deploy code:

const { exec } = require('child_process');

exec('git pull origin main && npm install && npm run build', (err, stdout, stderr)
=> {
if (err) {
console.error(`Error: ${stderr}`);
return;
}
console.log(`Deployment Success: ${stdout}`);
});

2. How can Node.js be used to write CI/CD pipeline scripts?

Answer:
CI/CD pipelines rely heavily on automation, and Node.js is ideal for writing scripts that
integrate with CI tools like Jenkins, GitHub Actions, and GitLab CI. Node.js scripts can
automate tasks such as running tests, building projects, and deploying code. The
asynchronous, non-blocking nature of Node.js ensures fast execution of multiple tasks.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
671

For Example:
The following Node.js script runs unit tests using the child_process module during the CI
phase:

const { exec } = require('child_process');

exec('npm test', (error, stdout, stderr) => {


if (error) {
console.error(`Test failed: ${stderr}`);
process.exit(1);
}
console.log(`Test passed: ${stdout}`);
});

3. What is a Command Line Interface (CLI), and how can you build one
using JavaScript?

Answer:
A Command Line Interface (CLI) allows users to interact with applications by typing
commands in the terminal. Using Node.js, developers can build lightweight CLIs for various
tasks like automating builds, deploying applications, or generating files.

For Example:
The following code creates a simple CLI tool using commander.js:

const { program } = require('commander');

program
.version('1.0.0')
.description('A simple CLI example')
.option('-n, --name <type>', 'Enter your name')
.action((options) => {
console.log(`Hello, ${options.name}!`);
});

program.parse(process.argv);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
672

4. What are some popular JavaScript build tools used in DevOps?

Answer:
JavaScript build tools like Webpack, Rollup, and Parcel are essential in DevOps for bundling,
optimizing, and packaging code. These tools help minimize file sizes, enable code splitting,
and support modular development.

● Webpack: Ideal for complex projects with multiple dependencies.


● Rollup: Known for tree-shaking and smaller builds, suitable for libraries.
● Parcel: Simple setup with zero-configuration for quick builds.

For Example:
Here is a basic Webpack configuration:

const path = require('path');

module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
};

5. How does npm contribute to DevOps automation?

Answer:
npm (Node Package Manager) is used to manage dependencies in JavaScript projects and
plays a key role in DevOps automation. With npm, developers can script tasks (like builds,
tests, and deployments) and manage versions of dependencies efficiently. It integrates
seamlessly with CI/CD pipelines to automate workflows.

For Example:
The following package.json file includes an automated test script:

{
"name": "devops-project",
"version": "1.0.0",
"scripts": {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
673

"test": "mocha tests/*.js"


},
"dependencies": {
"mocha": "^10.0.0"
}
}

6. What is the difference between npm, Yarn, and pnpm?

Answer:
npm, Yarn, and pnpm are package managers used to install, update, and manage project
dependencies.

● npm: Default package manager for Node.js, known for easy configuration.
● Yarn: Focuses on speed and stability, offering better caching than npm.
● pnpm: Uses symlinks to store packages efficiently, saving space and time.

For Example:
Install a package using:

npm install axios


yarn add axios
pnpm add axios

7. How does Webpack improve the efficiency of CI/CD pipelines?

Answer:
Webpack helps optimize JavaScript projects by bundling modules, enabling faster build and
deployment processes in CI/CD pipelines. It supports tree-shaking to remove unused code,
which reduces the bundle size and improves performance.

For Example:
A Webpack setup for production mode can be configured as:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
674

const path = require('path');

module.exports = {
mode: 'production',
entry: './src/app.js',
output: {
filename: 'app.bundle.js',
path: path.resolve(__dirname, 'dist'),
},
optimization: {
splitChunks: {
chunks: 'all',
},
},
};

8. What is the purpose of Rollup in JavaScript projects?

Answer:
Rollup is a build tool focused on creating efficient JavaScript bundles, especially for libraries.
It uses ES6 modules and supports tree-shaking, ensuring only the necessary code is bundled.
Rollup is widely used to generate smaller, optimized builds.

For Example:
Here is a basic Rollup configuration:

import resolve from '@rollup/plugin-node-resolve';

export default {
input: 'src/index.js',
output: {
file: 'bundle.js',
format: 'cjs',
},
plugins: [resolve()],
};

9. How does Parcel simplify JavaScript build processes?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
675

Answer:
Parcel offers a zero-configuration setup that simplifies JavaScript build processes. It
automatically detects dependencies and optimizes code without requiring complex
configuration files. This makes it ideal for small to medium-sized projects.

For Example:
Run the following command to start a Parcel project:

parcel index.

Parcel will bundle all related JavaScript, CSS, and assets.

10. How can JavaScript automate repetitive tasks in DevOps?

Answer:
JavaScript, with the help of Node.js, can automate repetitive tasks like file management,
monitoring, and deployment. Using scripting, developers can build tools that perform
operations such as cleaning directories, generating reports, or syncing files between servers.

For Example:
Here is a Node.js script that deletes log files older than 7 days:

const fs = require('fs');
const path = require('path');

const logDir = './logs';

fs.readdir(logDir, (err, files) => {


if (err) throw err;

files.forEach((file) => {
const filePath = path.join(logDir, file);
const stats = fs.statSync(filePath);
const now = new Date().getTime();
const endTime = new Date(stats.mtime).getTime() + 7 * 24 * 60 * 60 * 1000;

if (now > endTime) {


fs.unlink(filePath, (err) => {
if (err) throw err;

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
676

console.log(`${file} was deleted`);


});
}
});
});

11. How can Node.js be used to schedule automated tasks?

Answer:
Node.js supports task scheduling using libraries such as node-cron or agenda. These libraries
allow developers to automate tasks by running scripts at specific intervals, such as daily,
weekly, or monthly. Automated tasks are essential in DevOps for regular maintenance
activities like backups, log cleanup, or sending reports. node-cron offers a cron-like syntax to
specify the frequency, while agenda is useful for more complex job management and
integrates well with MongoDB.

For Example:
The following example uses node-cron to print a message every minute:

const cron = require('node-cron');

// Schedule a task to run every minute


cron.schedule('* * * * *', () => {
console.log('Task running every minute');
});

In the cron syntax, the asterisk (*) represents "every" for each unit of time (minute, hour, day,
etc.). This script runs indefinitely until stopped, useful for tasks like monitoring services.

12. What are npm scripts, and how are they useful for automation?

Answer:
npm scripts are commands defined in the package.json file to automate common tasks like
running tests, building projects, or starting servers. They simplify repetitive tasks and can
trigger multiple processes in sequence. Using npm scripts, developers can automate

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
677

workflows within CI/CD pipelines. These scripts are triggered with npm run <script_name>
and can call Node.js commands, shell commands, or other scripts.

For Example:
A sample package.json with npm scripts might look like this:

{
"scripts": {
"start": "node app.js",
"build": "webpack --mode production",
"test": "jest"
}
}

13. How does Node.js support shell commands execution in automation?

Answer:
Node.js can execute shell commands through the child_process module. This is especially
useful in DevOps for performing tasks like deploying code, starting/stopping servers, and
automating database backups. It allows JavaScript scripts to run shell commands and
process their outputs. You can use exec for simple commands or spawn for long-running
processes.

For Example:
Here is a script that lists all files in the current directory:

const { exec } = require('child_process');

exec('ls', (error, stdout, stderr) => {


if (error) {
console.error(`Error: ${stderr}`);
return;
}
console.log(`Files: ${stdout}`);
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
678

This script runs the ls command and prints the list of files.

14. How does Yarn improve dependency management in JavaScript


projects?

Answer:
Yarn is a package manager that enhances dependency management with faster installations
and a deterministic approach. It uses parallel installations and caching to improve speed.
Yarn ensures consistency across environments by generating a yarn.lock file, which locks
the exact versions of dependencies. This helps in maintaining the same behavior in
development, testing, and production.

For Example:
Here’s how to install and manage dependencies with Yarn:

yarn install # Installs all dependencies


yarn add axios # Adds a new dependency
yarn upgrade # Upgrades dependencies

Yarn’s offline cache allows installing packages even when disconnected from the internet.

15. What is the role of CI/CD pipelines in DevOps, and how does JavaScript
help?

Answer:
CI/CD pipelines automate the process of integrating code changes (CI) and deploying
applications (CD). JavaScript, particularly with Node.js, helps automate testing, code analysis,
and deployment in these pipelines. Scripts written in JavaScript can validate code, run tests,
and deploy builds automatically, ensuring quicker feedback loops and faster releases.

For Example:
A Node.js script for linting code during CI might look like this:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
679

const { exec } = require('child_process');

exec('eslint .', (error, stdout) => {


if (error) {
console.error(`Linting errors:\n${stdout}`);
process.exit(1);
}
console.log('No linting issues found.');
});

This script will exit with an error if linting fails, ensuring only clean code is deployed.

16. What is the difference between Webpack and Parcel?

Answer:
Webpack and Parcel are both JavaScript bundlers, but they differ in complexity and usage.

● Webpack: Best suited for large projects, offering extensive customization through
plugins and configuration. It requires a webpack.config.js file but provides
advanced features like tree-shaking, code splitting, and hot module replacement.
● Parcel: A zero-configuration bundler that works out of the box. It detects
dependencies automatically and is ideal for smaller projects or quick prototypes.

For Example:
Using Parcel for a quick build:

parcel build index.

Parcel will generate a production-ready bundle without additional configuration.

17. What is a build tool, and why is it important in JavaScript projects?

Answer:
A build tool automates tasks such as bundling, minifying, and transpiling code. It transforms
source code into optimized output suitable for deployment. Build tools like Webpack and

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
680

Parcel play a crucial role in improving application performance and ensuring that code is
ready for production.

For Example:
Running Webpack to bundle a project:

webpack --mode production

This command generates a minified and optimized build for deployment.

18. What are package.json and lock files, and how do they help in DevOps?

Answer:
The package.json file contains information about the project, including dependencies,
scripts, and metadata. Lock files, such as package-lock.json or yarn.lock, store the exact
versions of dependencies installed. This ensures that all environments (development, testing,
and production) use the same versions, preventing issues caused by version mismatches.

For Example:
Here’s a simple package.json:

{
"name": "my-project",
"version": "1.0.0",
"dependencies": {
"express": "^4.17.1"
}
}

The lock file ensures consistent behavior across different environments.

19. What are some best practices when writing JavaScript automation
scripts?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
681

Answer:
When writing JavaScript automation scripts:

● Use modular functions to keep code organized and maintainable.


● Handle errors gracefully to prevent crashes during execution.
● Use environment variables for sensitive information.
● Implement logging to track the behavior of scripts over time.

For Example:
Reading environment variables securely:

require('dotenv').config();

const apiKey = process.env.API_KEY;


if (!apiKey) {
console.error('API Key is missing.');
process.exit(1);
}

console.log(`Using API Key: ${apiKey}`);

This approach prevents hard-coding sensitive information.

20. How does pnpm differ from npm and Yarn in handling dependencies?

Answer:
pnpm manages dependencies differently by using a global store with symlinks to save disk
space and reduce duplication. Unlike npm and Yarn, pnpm ensures that a single version of a
package is shared across multiple projects, making installation faster and more efficient. This
is particularly useful for monorepos and large applications.

For Example:
To add a dependency using pnpm:

pnpm add express

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
682

This command installs the package and links it, reducing duplication across projects.

21. How does JavaScript help with serverless automation in DevOps?

Answer:
JavaScript, particularly with Node.js, plays a critical role in serverless automation by enabling
event-driven applications without needing dedicated infrastructure. Platforms such as AWS
Lambda, Azure Functions, and Google Cloud Functions support JavaScript functions that
execute in response to triggers like HTTP requests, database events, or file uploads. This
serverless model is highly cost-effective as it charges based only on function execution,
scaling automatically with demand.

For Example:
Here’s a sample AWS Lambda function written in JavaScript to send a notification:

exports.handler = async (event) => {


console.log('Triggered event:', event);
return {
statusCode: 200,
body: JSON.stringify({ message: 'Notification sent!' }),
};
};

This function runs only when triggered, making it ideal for automating workflows like file
processing or sending alerts based on events.

22. How can JavaScript automate cloud infrastructure management?

Answer:
JavaScript can automate cloud tasks using SDKs like aws-sdk for AWS and azure-sdk-for-
js for Azure. DevOps engineers use these libraries to script the deployment of cloud
resources, manage configurations, and automate scaling operations. Automating
infrastructure ensures consistency, reduces manual effort, and speeds up deployment
processes.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
683

For Example:
This Node.js script creates an S3 bucket using the AWS SDK:

const AWS = require('aws-sdk');


const s3 = new AWS.S3({ region: 'us-east-1' });

const params = { Bucket: 'my-new-s3-bucket' };

s3.createBucket(params, (err, data) => {


if (err) console.error('Error:', err);
else console.log('Bucket Created at:', data.Location);
});

This script creates cloud storage, demonstrating how JavaScript simplifies infrastructure
management.

23. How does Webpack’s tree-shaking feature optimize builds?

Answer:
Tree-shaking is an optimization process that removes unused JavaScript code from bundles
during the build process. Webpack leverages static analysis of ES6 modules to identify and
exclude unnecessary code. This reduces bundle size, improving performance by ensuring
that only the required code is delivered to users.

For Example:
Consider the following code:

// utils.js
export const usedFunction = () => console.log('Used');
export const unusedFunction = () => console.log('Unused');

// index.js
import { usedFunction } from './utils';
usedFunction();

During the build, Webpack excludes unusedFunction() from the output bundle.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
684

24. What are some best practices for writing JavaScript-based CLIs for
automation?

Answer:
When building JavaScript CLIs, it’s important to:

1. Follow Unix conventions: Return appropriate exit codes to signal success or failure.
2. Use established libraries: Use commander for handling command-line arguments or
inquirer for interactive prompts.
3. Provide helpful error messages: This makes troubleshooting easier.
4. Implement testing: Ensure command logic works under different scenarios.

For Example:
A simple CLI using commander:

const { program } = require('commander');

program
.version('1.0.0')
.description('Sample CLI Tool')
.option('-n, --name <name>', 'Enter your name')
.action((options) => {
console.log(`Hello, ${options.name}!`);
});

program.parse(process.argv);

This example creates a CLI that accepts a name and prints a greeting.

25. How can JavaScript-based monitoring tools assist in DevOps?

Answer:
Monitoring tools built with JavaScript and Node.js track metrics like CPU usage, memory
consumption, and application health. These tools can log data and integrate with services
like Prometheus or Grafana to create dashboards and send alerts. JavaScript's event-driven

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
685

model is well-suited for real-time monitoring, making it a good choice for tracking system
performance.

For Example:
A simple Node.js script that logs memory usage:

setInterval(() => {
const memoryUsage = process.memoryUsage();
console.log(`Memory Usage: ${JSON.stringify(memoryUsage)}`);
}, 5000);

This script logs memory statistics every 5 seconds, helping detect memory leaks.

26. What is hot module replacement (HMR), and how does it help in
DevOps?

Answer:
Hot Module Replacement (HMR) allows developers to update modules in a running
application without a full browser reload. It improves developer productivity by instantly
reflecting code changes, making it easier to test and debug. Webpack provides built-in HMR
support, which can be enabled in the configuration file.

For Example:
Enable HMR in Webpack:

module.exports = {
devServer: {
hot: true,
contentBase: './dist',
},
};

This setup makes development faster by only reloading modified modules.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
686

27. How does JavaScript facilitate task parallelization in CI/CD pipelines?

Answer:
JavaScript’s asynchronous nature enables task parallelization in CI/CD pipelines. Functions
like Promise.all() allow multiple independent tasks to run concurrently, reducing pipeline
execution time. This is especially useful for running tests, builds, or deployments
simultaneously.

For Example:
Running two tasks in parallel:

const { exec } = require('child_process');

const task1 = () => new Promise((resolve) => exec('npm test', resolve));


const task2 = () => new Promise((resolve) => exec('npm run lint', resolve));

Promise.all([task1(), task2()])
.then(() => console.log('Both tasks completed.'));

This script executes testing and linting tasks concurrently.

28. How does JavaScript handle file system operations for automation?

Answer:
JavaScript, via the fs module in Node.js, can manage file systems by reading, writing, and
modifying files and directories. Automating file operations is common in DevOps for log
rotation, configuration updates, and backups.

For Example:
Create a log file with the following script:

const fs = require('fs');

fs.writeFile('log.txt', 'Log entry created.', (err) => {


if (err) throw err;
console.log('Log file created.');
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
687

This example demonstrates how JavaScript can generate and manage files.

29. How can you implement error handling in JavaScript CI/CD automation
scripts?

Answer:
Error handling ensures that scripts do not fail silently and provides meaningful feedback to
developers. JavaScript offers try...catch blocks and error events to gracefully handle
exceptions. In CI/CD, proper error handling ensures failures are detected and logged for
troubleshooting.

For Example:
Here’s a script with error handling:

try {
const result = performCriticalTask();
console.log('Task completed successfully:', result);
} catch (error) {
console.error('Error encountered:', error.message);
process.exit(1); // Exit with failure status
}

This ensures that the pipeline reports errors instead of continuing silently.

30. How do JavaScript package managers contribute to DevOps


workflows?

Answer:
JavaScript package managers (npm, Yarn, pnpm) streamline dependency management by
automating installations and versioning. They ensure consistent environments by locking
dependency versions, which is essential for reproducible builds. Additionally, package
managers can run scripts for testing, building, and deploying code.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
688

For Example:
Here’s how npm can automate workflows:

npm install # Install dependencies


npm test # Run tests
npm run build # Build the project

This ensures dependencies are installed, tests are executed, and the project is built
automatically in CI/CD pipelines.

31. How does JavaScript enable Infrastructure-as-Code (IaC) automation?

Answer:
JavaScript, particularly with libraries like AWS CDK (Cloud Development Kit) and Pulumi,
enables Infrastructure-as-Code (IaC) by allowing developers to define cloud resources
through code. IaC allows infrastructure configurations to be versioned, shared, and reused.
Using JavaScript-based IaC tools, DevOps teams can automate the provisioning and
management of infrastructure resources consistently across environments.

For Example:
Here’s how to create an AWS S3 bucket using the AWS CDK:

const cdk = require('aws-cdk-lib');


const s3 = require('aws-cdk-lib/aws-s3');

class MyStack extends cdk.Stack {


constructor(scope, id, props) {
super(scope, id, props);

new s3.Bucket(this, 'MyBucket', {


versioned: true,
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
}
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
689

const app = new cdk.App();


new MyStack(app, 'MyAppStack');

32. How does JavaScript help with container automation using Docker?

Answer:
JavaScript, through Node.js scripts, can automate container management using Docker APIs
and shell commands. Automation tasks may include building, running, and stopping
containers, as well as deploying services in a containerized environment. This integration
simplifies DevOps workflows by streamlining container orchestration.

For Example:
A Node.js script to automate Docker container management:

const { exec } = require('child_process');

exec('docker build -t my-app . && docker run -d -p 3000:3000 my-app',


(error, stdout, stderr) => {
if (error) {
console.error(`Error: ${stderr}`);
return;
}
console.log(`Docker output: ${stdout}`);
});

This script builds and runs a Docker container for the application.

33. How does JavaScript support multithreading in automation workflows?

Answer:
While JavaScript is single-threaded by nature, Node.js supports multithreading through the
Worker Threads API. In DevOps, multithreading helps execute parallel tasks efficiently, such
as processing large datasets, file operations, or running multiple automation tasks
simultaneously.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
690

For Example:
Using worker threads for parallel execution:

const { Worker } = require('worker_threads');

const worker = new Worker(`


const { parentPort } = require('worker_threads');
parentPort.postMessage('Task complete');
`, { eval: true });

worker.on('message', (message) => {


console.log(`Worker: ${message}`);
});

34. How can JavaScript automate CI/CD pipelines with GitHub Actions?

Answer:
GitHub Actions allows developers to automate workflows directly from repositories.
JavaScript, used with Node.js and npm, is often leveraged in GitHub Actions to run tests,
build projects, and deploy applications automatically. Actions are defined in YAML files and
triggered by events like pull requests or merges.

For Example:
A GitHub Actions workflow for a Node.js project:

name: CI Pipeline

on: [push]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: npm install
- name: Run tests
run: npm test

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
691

35. How does JavaScript automate cloud deployments with continuous


delivery?

Answer:
JavaScript, along with tools like the AWS SDK, Azure CLI, or Firebase CLI, automates cloud
deployments by triggering updates in response to new code releases. This ensures seamless
continuous delivery (CD) by deploying new versions of the application without manual
intervention.

For Example:
Deploying an app using the Firebase CLI:

firebase deploy --only hosting

With JavaScript, you can integrate such commands into automation scripts to deploy
continuously as part of your CI/CD process.

36. What is the role of JavaScript in Kubernetes automation?

Answer:
JavaScript can automate Kubernetes operations by interacting with Kubernetes APIs. Using
libraries like kubernetes-client, developers can manage pods, services, and deployments
programmatically. This is useful for automating scaling, deployments, and rollbacks in a
Kubernetes cluster.

For Example:
A Node.js script to list Kubernetes pods:

const k8s = require('@kubernetes/client-node');


const kc = new k8s.KubeConfig();
kc.loadFromDefault();

const k8sApi = kc.makeApiClient(k8s.CoreV1Api);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
692

k8sApi.listNamespacedPod('default').then((res) => {
console.log('Pods:', res.body.items);
});

37. How does JavaScript help in automating security checks in DevOps?

Answer:
JavaScript-based tools like npm audit and Snyk detect vulnerabilities in dependencies and
automate security checks in CI/CD pipelines. By integrating these tools, DevOps teams can
ensure that applications remain secure throughout the development lifecycle.

For Example:
To run an automated security scan:

npm audit

This command checks for known vulnerabilities in dependencies and suggests fixes.

38. How can JavaScript automate versioning in CI/CD pipelines?

Answer:
Automating versioning ensures that every new build or release is correctly tagged. JavaScript
scripts can increment versions in the package.json file and commit the changes to version
control, ensuring consistent version management across releases.

For Example:
A script to automate version bumping:

const fs = require('fs');
const packageJson = require('./package.json');

packageJson.version = packageJson.version.replace(/(\d+)$/, (match) => +match + 1);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
693

fs.writeFile('./package.json', JSON.stringify(packageJson, null, 2), (err) => {


if (err) throw err;
console.log(`Version bumped to ${packageJson.version}`);
});

39. How does JavaScript support blue-green deployments in DevOps?

Answer:
Blue-green deployments minimize downtime during releases by maintaining two identical
environments—one live (blue) and one staged (green). JavaScript can automate switching
between environments using API calls or cloud management scripts, ensuring smooth
transitions.

For Example:
A Node.js script to update DNS records and switch environments:

const AWS = require('aws-sdk');


const route53 = new AWS.Route53();

const params = {
ChangeBatch: {
Changes: [{
Action: 'UPSERT',
ResourceRecordSet: {
Name: 'myapp.example.com',
Type: 'CNAME',
TTL: 300,
ResourceRecords: [{ Value: 'green-app.example.com' }],
},
}],
},
HostedZoneId: 'Z3M3LMPEXAMPLE',
};

route53.changeResourceRecordSets(params, (err, data) => {


if (err) console.error('Error updating DNS:', err);
else console.log('Environment switched:', data);
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
694

40. How does JavaScript integrate with message queues for automation?

Answer:
Message queues like RabbitMQ, Kafka, or AWS SQS are essential for decoupling services in
distributed systems. JavaScript, using libraries such as amqplib or AWS SDK, can automate
the production and consumption of messages in these queues, enabling asynchronous
communication between microservices.

For Example:
A Node.js script to publish messages to RabbitMQ:

const amqp = require('amqplib');

(async () => {
const connection = await amqp.connect('amqp://localhost');
const channel = await connection.createChannel();
const queue = 'task_queue';

channel.assertQueue(queue, { durable: true });


channel.sendToQueue(queue, Buffer.from('Hello from JavaScript!'), { persistent:
true });

console.log('Message sent');
setTimeout(() => connection.close(), 500);
})();

This script connects to RabbitMQ, sends a message, and closes the connection.

SCENARIO QUESTIONS

41. Scenario: Automating Deployment in a CI/CD Pipeline

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
695

Question: How would you use Node.js to automate deployment in a CI/CD pipeline?

Answer:
In CI/CD pipelines, deployment tasks can be automated using Node.js to eliminate the need
for manual operations. This ensures that every release follows the same process, reducing
human errors. A Node.js deployment script integrates with tools like GitHub Actions,
Jenkins, or GitLab CI to fetch code from repositories, install dependencies, build the project,
and restart the application automatically. It also ensures that each step—like testing and
building—is executed in the right sequence.

For Example:
The following Node.js script pulls code, installs dependencies, builds the project, and restarts
the server:

const { exec } = require('child_process');

exec('git pull origin main && npm install && npm run build && pm2 restart my-app',
(error, stdout, stderr) => {
if (error) {
console.error(`Error during deployment: ${stderr}`);
return;
}
console.log(`Deployment Successful: ${stdout}`);
});

This script automates code deployment and ensures that the server restarts with the latest
changes after each build.

42. Scenario: Automating Testing in CI/CD with npm Scripts

Question: How can npm scripts be used to automate testing as part of the CI/CD process?

Answer:
npm scripts streamline the process of running tests by defining commands in the
package.json file. During CI/CD pipelines, these scripts can be triggered to execute unit
tests, integration tests, or end-to-end tests automatically. Ensuring that tests run on every
code change helps maintain code quality and reduces the chances of deploying faulty code.
This practice, known as shift-left testing, catches bugs early in the development lifecycle.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
696

For Example:
Here is a package.json file with a test script defined:

{
"scripts": {
"test": "mocha tests/**/*.js"
}
}

This ensures that the tests are executed in every build process to validate the changes before
deployment.

43. Scenario: Creating a Custom CLI Tool to Manage Builds

Question: How would you use Node.js to create a custom CLI tool for managing builds?

Answer:
A Command Line Interface (CLI) tool allows developers to perform common operations,
such as building or deploying applications, from the terminal. Using libraries like commander,
you can create a lightweight tool to abstract complex build tasks into simple commands. This
makes workflows faster and more efficient by minimizing human interaction with multiple
tools.

For Example:
Below is a simple CLI built with commander to manage builds:

const { program } = require('commander');

program
.command('build')
.description('Build the project')
.action(() => {
console.log('Building the project...');
// Additional build logic here
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
697

program.parse(process.argv);

Run the command:

node cli.js build

This triggers the build process with minimal effort from the developer.

44. Scenario: Optimizing Build Size with Webpack in CI/CD

Question: How can Webpack be used to optimize build size in a CI/CD environment?

Answer:
In a CI/CD environment, optimizing build size ensures that applications load faster and
provide better user experience. Webpack enables code splitting and tree-shaking to reduce
the size of JavaScript bundles by including only the necessary modules. In production builds,
Webpack minifies the code and removes unused imports, which results in faster
deployments and improved application performance.

For Example:
Here is a Webpack configuration that uses tree-shaking:

const path = require('path');

module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
optimization: {
usedExports: true, // Enables tree-shaking
},
};

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
698

This configuration ensures only the used exports are included in the build.

45. Scenario: Automating Dependency Management with Yarn

Question: How would you automate dependency management using Yarn in a project?

Answer:
Yarn improves dependency management by providing faster installations and ensuring
version consistency across environments using the yarn.lock file. Automation in CI/CD
pipelines can be achieved by using Yarn to install dependencies before building and testing
the project. This ensures that developers always work with the correct versions of libraries,
avoiding dependency conflicts.

For Example:
Define a CI/CD step to install dependencies:

yarn install

This command installs all dependencies listed in package.json and ensures the correct
versions are used every time the pipeline runs.

46. Scenario: Creating and Publishing a JavaScript Library with Rollup

Question: How would you use Rollup to bundle and publish a JavaScript library?

Answer:
Rollup is ideal for building JavaScript libraries because of its tree-shaking capabilities, which
ensure that only the necessary code is included in the bundle. After bundling the library, it
can be published to npm for public use or deployed privately. Rollup also supports multiple
formats like CommonJS (CJS) and ES modules (ESM), making the library compatible with
different environments.

For Example:
Here’s a Rollup configuration for bundling a library:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
699

export default {
input: 'src/index.js',
output: {
file: 'dist/bundle.js',
format: 'cjs',
},
};

47. Scenario: Automating File Operations for DevOps Tasks

Question: How can Node.js automate file operations, such as rotating logs?

Answer:
In DevOps, log rotation prevents disk space from being consumed by large or outdated logs.
Node.js automates this task by managing log files—moving older logs to an archive folder or
deleting them. This automation ensures that only recent logs are kept while preserving old
ones for future analysis.

For Example:
The following Node.js script rotates logs by archiving them:

const fs = require('fs');
const path = require('path');

const logDir = './logs';


const archiveDir = './logs/archive';

fs.readdir(logDir, (err, files) => {


if (err) throw err;

files.forEach((file) => {
const oldPath = path.join(logDir, file);
const newPath = path.join(archiveDir, file);
fs.rename(oldPath, newPath, (err) => {
if (err) throw err;
console.log(`${file} archived.`);
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
700

});
});

48. Scenario: Automating Releases with Versioning Scripts

Question: How would you automate version updates and releases using Node.js?

Answer:
Automating version updates ensures that every release follows a standardized versioning
strategy, such as Semantic Versioning (semver). Node.js scripts can increment the version in
package.json before committing the changes and tagging them in version control. This
ensures that the product is always released with unique version numbers.

For Example:
A script to bump the version number:

const fs = require('fs');
const packageJson = require('./package.json');

const newVersion = packageJson.version.replace(/(\d+)$/, (match) => +match + 1);


packageJson.version = newVersion;

fs.writeFileSync('./package.json', JSON.stringify(packageJson, null, 2));


console.log(`Version updated to ${newVersion}`);

49. Scenario: Handling Errors in Automation Scripts

Question: How would you implement error handling in a JavaScript automation script?

Answer:
Error handling is critical in automation scripts to ensure that failures are detected and
reported promptly. Node.js provides try...catch blocks for error handling. In CI/CD
pipelines, it’s essential to log errors and exit with appropriate status codes to prevent faulty
builds from being deployed.

For Example:
Here’s a Node.js script with proper error handling:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
701

try {
const result = performTask();
console.log('Task completed:', result);
} catch (error) {
console.error('Error occurred:', error.message);
process.exit(1); // Exit with failure status
}

50. Scenario: Implementing a Custom Build Tool with Parcel

Question: How would you create a simple build tool using Parcel?

Answer:
Parcel simplifies the build process by bundling assets without complex configuration. It
automatically detects the required dependencies and optimizes files for production. Parcel is
ideal for small to medium-sized projects where rapid development and quick builds are
necessary.

For Example:
Run the following commands to install Parcel and build your project:

npm install --global parcel-bundler


parcel build index.

This command generates a production-ready build with optimized JavaScript, HTML, and
CSS assets.

51. Scenario: Automating API Calls in a CI/CD Pipeline

Question: How would you use Node.js to automate API calls in a CI/CD pipeline?

Answer:
Node.js can be used to automate interactions with external APIs, which is helpful in CI/CD
pipelines for tasks like triggering deployments, sending notifications, or updating a

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
702

dashboard. The axios library simplifies sending HTTP requests from Node.js scripts.
Automating API calls ensures that required actions are performed consistently as part of the
pipeline, such as informing a service of a new release.

For Example:
Here is a Node.js script to send a POST request to notify a deployment:

const axios = require('axios');

const notifyDeployment = async () => {


try {
const response = await axios.post('https://example.com/api/deployments', {
version: '1.2.0',
status: 'successful',
});
console.log('Notification Sent:', response.data);
} catch (error) {
console.error('Error Sending Notification:', error.message);
}
};

notifyDeployment();

52. Scenario: Monitoring Application Logs Using Node.js

Question: How can you use Node.js to monitor and analyze application logs?

Answer:
Node.js can monitor logs in real-time by reading log files and analyzing their contents. This is
helpful in DevOps for tracking errors, identifying performance issues, and ensuring smooth
operations. Log monitoring scripts can be integrated into dashboards or alerting systems for
proactive incident management.

For Example:
A Node.js script to watch and print new log entries:

const fs = require('fs');

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
703

const logFile = './logs/app.log';


fs.watch(logFile, (eventType, filename) => {
if (eventType === 'change') {
const logContent = fs.readFileSync(logFile, 'utf8');
console.log('New Log Entry:', logContent);
}
});

53. Scenario: Building a Static Website with Parcel

Question: How would you use Parcel to bundle a static website?

Answer:
Parcel is a zero-configuration bundler that makes it easy to build static websites. It handles
JavaScript, CSS, HTML, and other assets, optimizing them for production. Using Parcel,
developers can build and deploy static sites quickly.

For Example:
Bundle and optimize a static site with the following command:

parcel build index.

Parcel automatically detects the dependencies and creates an optimized build in the dist
directory, ready for deployment.

54. Scenario: Scheduling a Cron Job with Node.js

Question: How would you schedule a task to run at specific intervals using Node.js?

Answer:
Using the node-cron library, you can schedule tasks to run at specific intervals, such as hourly
or daily. This is useful for automating routine tasks like backups, log rotations, or data
synchronization in DevOps.

For Example:
A Node.js script to run a task every minute:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
704

const cron = require('node-cron');

// Schedule a task to run every minute


cron.schedule('* * * * *', () => {
console.log('Task executed at', new Date());
});

55. Scenario: Automating Package Version Checks with npm

Question: How can you use npm to automate version checks for dependencies?

Answer:
Using npm outdated or npm-check, developers can automate the process of checking for
outdated dependencies. Keeping dependencies up-to-date helps maintain security and
ensures compatibility with the latest tools.

For Example:
Run the following command to check outdated packages:

npm outdated

This command lists all dependencies with available updates.

56. Scenario: Managing Multiple Package Versions with pnpm

Question: How does pnpm help manage multiple package versions efficiently?

Answer:
pnpm stores packages in a global content-addressable store and creates symlinks to them in
projects. This reduces disk space usage and ensures that different versions of a package are
efficiently managed without duplication.

For Example:
Install a specific version of a package with pnpm:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
705

pnpm add [email protected]

This ensures that the specified version is linked and reused across multiple projects.

57. Scenario: Minifying CSS and JavaScript with Parcel

Question: How does Parcel handle CSS and JavaScript minification?

Answer:
Parcel automatically minifies CSS and JavaScript files in production builds, reducing the size
of the bundled files and improving application performance. This ensures that only optimized
code is deployed.

For Example:
Create a minified build with:

parcel build index. --no-source-maps

The output will contain optimized files, ready for production.

58. Scenario: Using Node.js to Clean Up Temporary Files

Question: How can Node.js automate the cleanup of temporary files?

Answer:
Node.js can be used to identify and delete temporary files periodically, preventing
unnecessary disk usage. This cleanup can be scheduled as part of a DevOps routine.

For Example:
A script to delete all files from a temporary folder:

const fs = require('fs');

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
706

const path = require('path');

const tempDir = './temp';

fs.readdir(tempDir, (err, files) => {


if (err) throw err;

files.forEach((file) => {
fs.unlink(path.join(tempDir, file), (err) => {
if (err) throw err;
console.log(`${file} deleted.`);
});
});
});

59. Scenario: Integrating Webpack into a JavaScript Project

Question: How would you integrate Webpack into a JavaScript project?

Answer:
Webpack bundles JavaScript files along with other assets like CSS and images, making it
ideal for large projects. Configuring Webpack allows for modular code development and
optimized production builds.

For Example:
Here’s a basic Webpack configuration:

const path = require('path');

module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
};

Run the following command to bundle the project:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
707

webpack --mode production

60. Scenario: Automating Server Monitoring with Node.js

Question: How can Node.js automate server monitoring tasks?

Answer:
Node.js can collect and log server metrics such as CPU usage, memory consumption, and
disk space, providing insights into server performance. These metrics can be used to trigger
alerts or generate reports, ensuring proactive monitoring.

For Example:
A script to log memory usage every 5 seconds:

setInterval(() => {
const memoryUsage = process.memoryUsage();
console.log('Memory Usage:', memoryUsage);
}, 5000);

This script logs memory usage, helping identify memory leaks or performance issues.

61. Scenario: Automating Container Management with Docker and Node.js

Question: How would you use Node.js to automate Docker container management?

Answer:
Node.js can interact with Docker through the child_process module or Docker’s APIs to
automate tasks such as building, running, stopping, and removing containers. This is
especially useful in DevOps workflows for continuous integration and deployment.
Automating container management ensures that the containers are always up-to-date and
eliminates the need for manual intervention.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
708

For Example:
Here is a script to automate Docker container creation and startup:

const { exec } = require('child_process');

exec('docker build -t my-app . && docker run -d -p 3000:3000 my-app',


(error, stdout, stderr) => {
if (error) {
console.error(`Error: ${stderr}`);
return;
}
console.log(`Docker output: ${stdout}`);
});

This script builds a Docker image and runs the container on port 3000.

62. Scenario: Managing Kubernetes Pods with Node.js

Question: How can Node.js automate Kubernetes pod management?

Answer:
Node.js can manage Kubernetes resources programmatically using the
@kubernetes/client-node library. This allows automation of tasks such as creating,
updating, and monitoring pods, which is crucial in DevOps for scaling and maintaining
applications in a Kubernetes cluster.

For Example:
Below is a script to list all pods in the default namespace:

const k8s = require('@kubernetes/client-node');


const kc = new k8s.KubeConfig();
kc.loadFromDefault();

const k8sApi = kc.makeApiClient(k8s.CoreV1Api);

k8sApi.listNamespacedPod('default').then((res) => {
console.log('Pods:', res.body.items);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
709

});

This script retrieves and prints the list of running pods.

63. Scenario: Automating Cloud Functions Deployment Using Node.js

Question: How would you automate the deployment of cloud functions with Node.js?

Answer:
Node.js can automate the deployment of serverless cloud functions by integrating with tools
like the AWS SDK, Firebase CLI, or Google Cloud SDK. This allows developers to deploy
functions as part of a CI/CD pipeline, ensuring rapid and consistent updates.

For Example:
Here’s a Firebase deployment command automated using Node.js:

const { exec } = require('child_process');

exec('firebase deploy --only functions', (error, stdout, stderr) => {


if (error) {
console.error(`Deployment Error: ${stderr}`);
return;
}
console.log(`Deployment Successful: ${stdout}`);
});

64. Scenario: Automating DNS Updates for Blue-Green Deployments

Question: How would you automate DNS updates for blue-green deployments with Node.js?

Answer:
In blue-green deployments, DNS updates ensure traffic is smoothly redirected between the
two environments. Node.js can automate these updates by interacting with cloud DNS
services like AWS Route 53. This eliminates downtime by switching traffic seamlessly from
the old version to the new one.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
710

For Example:
Below is a Node.js script to update DNS records in AWS Route 53:

const AWS = require('aws-sdk');


const route53 = new AWS.Route53();

const params = {
ChangeBatch: {
Changes: [{
Action: 'UPSERT',
ResourceRecordSet: {
Name: 'myapp.example.com',
Type: 'CNAME',
TTL: 300,
ResourceRecords: [{ Value: 'new-app.example.com' }],
},
}],
},
HostedZoneId: 'Z3M3LMPEXAMPLE',
};

route53.changeResourceRecordSets(params, (err, data) => {


if (err) console.error('DNS Update Error:', err);
else console.log('DNS Updated:', data);
});

65. Scenario: Automating Build and Deployments Using GitHub Actions


with JavaScript

Question: How can you automate build and deployments using JavaScript with GitHub
Actions?

Answer:
GitHub Actions allows you to define workflows that automate build and deployment
processes. JavaScript-based actions can trigger builds, run tests, and deploy applications in
response to code changes. This ensures consistent and error-free deployments directly from
the repository.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
711

For Example:
Below is a GitHub Actions workflow for a Node.js project:

name: Build and Deploy

on: [push]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: npm install
- name: Run tests
run: npm test
- name: Deploy
run: npm run deploy

66. Scenario: Automating Versioning with Git Tags in CI/CD Pipelines

Question: How can you automate version tagging using Node.js in CI/CD pipelines?

Answer:
Versioning is critical in CI/CD to ensure each release is uniquely identifiable. Node.js can
automate version increments and tagging with Git. Automating this process ensures that
every build is correctly tracked and versioned.

For Example:
Below is a Node.js script to bump the version and tag it in Git:

const { execSync } = require('child_process');


const packageJson = require('./package.json');

const newVersion = packageJson.version.replace(/(\d+)$/, (match) => +match + 1);


packageJson.version = newVersion;
require('fs').writeFileSync('./package.json', JSON.stringify(packageJson, null,
2));

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
712

execSync(`git add package.json && git commit -m "Bump version to ${newVersion}"`);


execSync(`git tag v${newVersion} && git push origin --tags`);
console.log(`Version bumped to ${newVersion} and tagged.`);

67. Scenario: Implementing Multi-Environment Builds with Webpack

Question: How would you implement multi-environment builds using Webpack?

Answer:
Multi-environment builds allow applications to be configured differently for development,
testing, and production. Webpack supports this through environment variables and
separate configurations, ensuring each build matches the environment’s requirements.

For Example:
Below is a Webpack configuration that uses environment variables:

const path = require('path');


const webpack = require('webpack');

module.exports = (env) => ({


entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(env.NODE_ENV),
}),
],
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
713

68. Scenario: Handling Long-Running Processes with Node.js Worker


Threads

Question: How would you handle long-running processes in Node.js?

Answer:
Long-running processes in Node.js can block the event loop, impacting performance. Using
the Worker Threads API, you can offload heavy computations to separate threads, ensuring
the main thread remains responsive.

For Example:
Here is a script using worker threads for a long-running task:

const { Worker } = require('worker_threads');

const worker = new Worker(`


const { parentPort } = require('worker_threads');
let result = 0;
for (let i = 0; i < 1e9; i++) result += i;
parentPort.postMessage(result);
`, { eval: true });

worker.on('message', (msg) => console.log(`Result: ${msg}`));

69. Scenario: Automating Security Audits with npm and Snyk

Question: How can JavaScript automate security audits using npm and Snyk?

Answer:
Automated security audits ensure that applications are free from known vulnerabilities. npm
provides built-in auditing, while Snyk offers deeper insights into dependencies. These tools
can be integrated into CI/CD pipelines to block deployments with vulnerabilities.

For Example:
Run an npm security audit:

npm audit

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
714

Integrate Snyk in the pipeline:

snyk test

70. Scenario: Automating Rollbacks with Node.js in Case of Deployment


Failures

Question: How would you automate rollbacks using Node.js if a deployment fails?

Answer:
Automating rollbacks ensures that faulty deployments are quickly undone to maintain
service availability. Node.js can interact with version control systems and infrastructure tools
to revert to the last known stable state if an error occurs during deployment.

For Example:
Here’s a script that reverts to the previous Git commit:

const { exec } = require('child_process');

exec('git reset --hard HEAD~1 && pm2 restart my-app', (error, stdout, stderr) => {
if (error) {
console.error(`Rollback Error: ${stderr}`);
return;
}
console.log('Rollback Successful:', stdout);
});

71. Scenario: Automating CI/CD Pipeline Failure Notifications Using Node.js

Question: How would you automate failure notifications for a CI/CD pipeline using Node.js?

Answer:
Automating notifications ensures that DevOps teams are alerted immediately when a
pipeline fails, allowing them to respond quickly. Node.js can send alerts to messaging

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
715

services like Slack, Microsoft Teams, or email using APIs. This ensures faster recovery from
pipeline issues.

For Example:
Here’s a Node.js script to send a Slack message when a failure occurs:

const axios = require('axios');

const sendSlackNotification = async (message) => {


try {
await axios.post('https://hooks.slack.com/services/your/webhook/url', {
text: message,
});
console.log('Notification sent');
} catch (error) {
console.error('Error sending notification:', error.message);
}
};

sendSlackNotification('CI/CD pipeline failed! Immediate action required.');

72. Scenario: Implementing Blue-Green Deployment Rollbacks Using


Node.js

Question: How would you automate rollbacks in a blue-green deployment strategy using
Node.js?

Answer:
In a blue-green deployment strategy, Node.js can automate the rollback process by
switching traffic back to the stable environment if the new version fails. This ensures minimal
downtime and avoids disruptions in production services. The automation interacts with DNS
or load balancers to switch environments.

For Example:
Here’s a script that switches traffic back to the blue environment:

const AWS = require('aws-sdk');


const route53 = new AWS.Route53();

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
716

const params = {
ChangeBatch: {
Changes: [{
Action: 'UPSERT',
ResourceRecordSet: {
Name: 'myapp.example.com',
Type: 'CNAME',
TTL: 300,
ResourceRecords: [{ Value: 'blue-app.example.com' }],
},
}],
},
HostedZoneId: 'Z3M3LMPEXAMPLE',
};

route53.changeResourceRecordSets(params, (err, data) => {


if (err) console.error('Rollback Error:', err);
else console.log('Rollback to blue environment successful:', data);
});

73. Scenario: Automating Server Health Checks Using Node.js

Question: How would you use Node.js to perform automated server health checks?

Answer:
Automated health checks ensure that servers and services are running as expected. Node.js
can periodically send HTTP requests to endpoints and alert the team if any service is
unresponsive. This is essential for proactive monitoring in DevOps environments.

For Example:
A script to check the health of a server:

const axios = require('axios');

const checkServerHealth = async () => {


try {
const response = await axios.get('https://example.com/health');
if (response.status === 200) {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
717

console.log('Server is healthy');
}
} catch (error) {
console.error('Server is down:', error.message);
}
};

setInterval(checkServerHealth, 30000); // Check every 30 seconds

74. Scenario: Automating Backup Creation and Upload to Cloud Using


Node.js

Question: How can you automate the creation and upload of backups to a cloud storage
service using Node.js?

Answer:
Node.js can automate the backup process by compressing files and uploading them to cloud
services like AWS S3 or Google Cloud Storage. Automating backups ensures data is
consistently saved without manual intervention, reducing the risk of data loss.

For Example:
Here’s a script to upload a backup to AWS S3:

const AWS = require('aws-sdk');


const fs = require('fs');
const s3 = new AWS.S3();

const uploadBackup = (filePath, bucketName) => {


const fileStream = fs.createReadStream(filePath);
const params = { Bucket: bucketName, Key: 'backup.zip', Body: fileStream };

s3.upload(params, (err, data) => {


if (err) console.error('Upload Error:', err);
else console.log('Backup uploaded:', data.Location);
});
};

uploadBackup('./backup.zip', 'my-s3-bucket');

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
718

75. Scenario: Automating Dependency Updates in a CI/CD Pipeline Using


Node.js

Question: How would you automate dependency updates in a CI/CD pipeline with Node.js?

Answer:
Automating dependency updates ensures that your application stays secure and up-to-date
with the latest libraries. Node.js can automate this process by running npm outdated and
updating dependencies during the build process.

For Example:
A script to update all outdated dependencies:

const { exec } = require('child_process');

exec('npm update', (error, stdout, stderr) => {


if (error) {
console.error('Error updating dependencies:', stderr);
return;
}
console.log('Dependencies updated:', stdout);
});

76. Scenario: Automating Multi-Environment Configuration Using Node.js

Question: How would you manage multi-environment configurations using Node.js?

Answer:
Managing configurations for multiple environments (e.g., development, staging, production)
ensures the right settings are applied for each. Node.js can use dotenv to load different
environment variables based on the deployment stage.

For Example:
Here’s a setup to load environment-specific variables:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
719

require('dotenv').config({ path: `./env/.${process.env.NODE_ENV}.env` });

console.log(`Running in ${process.env.NODE_ENV} mode`);


console.log(`API URL: ${process.env.API_URL}`);

77. Scenario: Automating Load Testing with Node.js

Question: How would you automate load testing for an application using Node.js?

Answer:
Automating load testing helps ensure that your application can handle high traffic. Node.js
can integrate with tools like Artillery or k6 to automate load tests, providing insights into the
system’s performance under stress.

For Example:
Install Artillery and run a load test:

npm install -g artillery


artillery quick --count 10 -n 20 https://example.com

This command simulates 10 users making 20 requests each.

78. Scenario: Automating Git Operations Using Node.js

Question: How can Node.js automate common Git operations?

Answer:
Automating Git operations (e.g., pulling, committing, and pushing changes) saves time and
reduces human error. Node.js can trigger these commands through the child_process
module, enabling seamless version control as part of automation workflows.

For Example:
Here’s a script to commit and push changes:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
720

const { exec } = require('child_process');

exec('git add . && git commit -m "Automated commit" && git push',
(error, stdout, stderr) => {
if (error) {
console.error('Git Error:', stderr);
return;
}
console.log('Git Output:', stdout);
});

79. Scenario: Automating Code Quality Checks with ESLint in CI/CD

Question: How would you integrate ESLint into a CI/CD pipeline to automate code quality
checks?

Answer:
Automating code quality checks with ESLint ensures that coding standards are maintained
across the project. This can be integrated into CI/CD pipelines to prevent code with style
violations from being merged or deployed.

For Example:
Define an npm script to run ESLint:

{
"scripts": {
"lint": "eslint ."
}
}

Run the following command during the pipeline:

npm run lint

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
721

80. Scenario: Automating Application Scaling with Node.js and AWS SDK

Question: How would you automate scaling operations using Node.js and the AWS SDK?

Answer:
Node.js can automate application scaling by interacting with AWS Auto Scaling groups. This
ensures that resources scale dynamically based on demand, maintaining performance
during traffic spikes.

For Example:
A script to update the desired instance count in an auto-scaling group:

const AWS = require('aws-sdk');


const autoscaling = new AWS.AutoScaling();

const params = {
AutoScalingGroupName: 'my-auto-scaling-group',
DesiredCapacity: 5,
};

autoscaling.setDesiredCapacity(params, (err, data) => {


if (err) console.error('Scaling Error:', err);
else console.log('Scaling Updated:', data);
});

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
722

Chapter 13: Interview Preparation: Practice


Problems and Coding Questions

THEORETICAL QUESTIONS
1. What is JavaScript and how is it different from Java?

Answer:
JavaScript is a high-level, dynamic programming language used primarily for web
development. It was initially created to add interactivity to websites and is now widely used
in both frontend and backend development (via Node.js). JavaScript runs in web browsers
and has become a fundamental part of the web’s architecture alongside HTML and CSS.

Although JavaScript and Java share similar names, they are different in purpose and design.
JavaScript is dynamically typed, interpreted, and supports prototype-based inheritance. In
contrast, Java is statically typed, compiled, and follows class-based object-oriented principles.
Java is used for large-scale backend systems, Android apps, and enterprise software, while
JavaScript focuses more on web interactivity.

For Example:

// Example of JavaScript function to validate a form input


function validateForm() {
let name = document.getElementById("name").value;
if (name === "") {
alert("Name is required!");
return false;
}
}

In this example, the JavaScript function interacts with HTML elements to provide real-time
input validation. Java would not be suitable for this task, as it operates on servers or devices,
not directly within the browser.

2. What are JavaScript Data Types?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
723

Answer:
JavaScript supports several data types to handle different kinds of values. These data types
are broadly categorized into primitive and non-primitive types.

1. Primitive data types:


○ String: Represents text (e.g., "Shardul").
○ Number: Includes both integer and floating-point numbers (e.g., 3.14).
○ Boolean: Represents logical values (true or false).
○ Undefined: Assigned to variables that are declared but not initialized.
○ Null: Represents the intentional absence of any object value.
○ Symbol: A unique and immutable data type introduced in ES6.
2. Non-primitive data types:
○ Object: A collection of key-value pairs (e.g., {name: "Shardul"}).
○ Array: A special type of object to store ordered lists of values.
○ Function: A block of reusable code.

For Example:

let name = "Shardul"; // String


let age = 25; // Number
let isDeveloper = true; // Boolean
let skills; // Undefined (not initialized)
let result = null; // Null value

console.log(typeof name); // Output: string


console.log(typeof skills); // Output: undefined

In this example, variables with different data types are created to demonstrate how
JavaScript handles them.

3. What is a closure in JavaScript?

Answer:
A closure occurs when a function retains access to variables from its parent scope, even after
the outer function has finished executing. Closures allow inner functions to "remember" the
environment they were created in, enabling encapsulation and data privacy.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
724

Closures are used extensively in JavaScript to create private variables that are inaccessible
from outside their enclosing functions. This concept is useful for creating modular and
reusable code.

For Example:

function outerFunction(outerVariable) {
return function innerFunction(innerVariable) {
console.log(`Outer: ${outerVariable}, Inner: ${innerVariable}`);
};
}

const closureInstance = outerFunction("outside");


closureInstance("inside"); // Output: Outer: outside, Inner: inside

Here, innerFunction forms a closure by capturing outerVariable from the outer scope.
Even though outerFunction has returned, innerFunction can still access outerVariable.

4. Explain the difference between let, const, and var.

Answer:
JavaScript provides three ways to declare variables: var, let, and const. Each has different
behavior regarding scoping, hoisting, and reassignment.

1. var:
○ Scoped to the function in which it is declared or global if outside a function.
○ Can be redeclared and reassigned.
○ Hoisted to the top of its scope but initialized with undefined.
2. let:
○ Block-scoped (only accessible within the enclosing {} block).
○ Cannot be redeclared within the same scope, but can be reassigned.
○ Not initialized until the declaration is reached.
3. const:
○ Block-scoped like let.
○ Cannot be reassigned or redeclared.
○ Must be initialized during declaration.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
725

var x = 1;
if (true) {
let y = 2;
const z = 3;
console.log(y, z); // Output: 2 3
}
console.log(x); // Output: 1

In this example, y and z are block-scoped to the if block, while x is accessible globally. Trying
to access y or z outside the block will throw an error.

5. What is an arrow function, and how is it different from a regular


function?

Answer:
An arrow function is a more concise way to define functions in JavaScript, introduced in ES6.
Unlike traditional functions, arrow functions do not have their own this context. Instead,
they inherit this from the outer scope, making them useful for callbacks and event handlers
where this needs to be preserved.

Arrow functions cannot be used as constructors and do not support the arguments object.

For Example:

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

// Arrow function
const multiply = (a, b) => a * b;

console.log(add(2, 3)); // Output: 5


console.log(multiply(2, 3)); // Output: 6

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
726

Here, the arrow function provides a shorter syntax for the same functionality as the regular
function.

6. What is the event loop in JavaScript?

Answer:
The event loop is the mechanism that allows JavaScript to perform non-blocking I/O
operations despite being single-threaded. It ensures that asynchronous tasks (e.g., timers,
API calls) do not block the main thread.

The event loop continuously checks if the call stack is empty. When it is, the event loop picks
up pending tasks from the task queue and pushes them onto the stack for execution.

For Example:

console.log("Start");

setTimeout(() => {
console.log("Inside timeout");
}, 0);

console.log("End");

// Output:
// Start
// End
// Inside timeout

Even though the timeout is set to 0, it gets queued and only runs after the current stack is
cleared.

7. What are promises in JavaScript?

Answer:
A promise represents the result of an asynchronous operation. It can be in one of three
states: pending, fulfilled, or rejected. Promises help in managing asynchronous code by
providing then() and catch() methods to handle the result.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
727

For Example:

const promise = new Promise((resolve, reject) => {


let success = true;
if (success) resolve("Operation succeeded");
else reject("Operation failed");
});

promise.then(result => console.log(result))


.catch(error => console.error(error));

This example demonstrates a simple promise that either resolves or rejects based on a
condition.

8. What is recursion, and how does it work in JavaScript?

Answer:
Recursion is when a function calls itself to solve a problem in smaller steps. A recursive
function must include a base case to prevent infinite recursion and stack overflow.

For Example:

function factorial(n) {
if (n === 0) return 1; // Base case
return n * factorial(n - 1);
}

console.log(factorial(5)); // Output: 120

This function calculates the factorial of a number using recursion.

9. What are JavaScript objects, and how do you create one?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
728

Answer:
JavaScript objects are collections of key-value pairs, often used to represent real-world
entities.

For Example:

const person = {
name: "Shardul",
age: 25,
greet() {
console.log(`Hello, ${this.name}`);
}
};

person.greet(); // Output: Hello, Shardul

This object stores properties (name and age) and a method (greet).

10. What is the difference between shallow copy and deep copy in
JavaScript?

Answer:
A shallow copy only duplicates the first-level properties, while a deep copy clones all levels,
ensuring independence from the original object.

For Example:

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

const deepCopy = JSON.parse(JSON.stringify(obj1));


deepCopy.b.c = 4;
console.log(obj1.b.c); // Output: 3

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
729

This demonstrates how changes to a nested object affect only the shallow copy.

11. What is hoisting in JavaScript?

Answer:
Hoisting is a JavaScript mechanism where variable and function declarations are moved to
the top of their scope before code execution. This means that even if you declare a function
or variable later in your code, it is known to the JavaScript engine at the start of execution.
However, only the declarations are hoisted, not the initializations.

● Variables declared with var are hoisted but initialized with undefined.
● Variables declared with let and const are also hoisted, but they remain in a temporal
dead zone (TDZ), meaning they cannot be accessed before their declaration is
encountered. Accessing them before initialization will result in a ReferenceError.
● Function declarations are fully hoisted, meaning you can call the function even before
the line where it is declared.

For Example:

console.log(greet()); // Output: Hello, World!

function greet() {
return "Hello, World!";
}

console.log(x); // Output: undefined (declaration is hoisted, but not


initialization)
var x = 5;

console.log(y); // Throws ReferenceError: Cannot access 'y' before initialization


let y = 10;

In this example:

● The function greet() is hoisted and works even when called before the declaration.
● x is declared using var and hoisted, but its value is undefined until initialized.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
730

● Trying to access y (declared with let) before its declaration results in an error because
it is in the TDZ.

12. What is the difference between synchronous and asynchronous code in


JavaScript?

Answer:
Synchronous code in JavaScript runs sequentially. Each line of code is executed one after the
other, and the program waits for a task to complete before moving to the next. This can
become problematic if a task takes too long, as it will block the main thread, making the
application unresponsive.

Asynchronous code, on the other hand, allows certain tasks to run in the background.
JavaScript can continue executing other code while waiting for the asynchronous task (such
as a network request or timer) to complete. Asynchronous operations are achieved using
callbacks, promises, or async/await syntax.

For Example:

// Synchronous example
console.log("Start");
for (let i = 0; i < 1000000000; i++) {} // Long loop, blocking execution
console.log("End");

// Asynchronous example
console.log("Start");
setTimeout(() => console.log("After 2 seconds"), 2000); // Non-blocking
console.log("End");

// Output for asynchronous code:


// Start
// End
// After 2 seconds

In the asynchronous example, the setTimeout function schedules a task to run after 2
seconds but allows the rest of the code to continue running without waiting.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
731

13. What is the difference between == and === in JavaScript?

Answer:
The == (loose equality) and === (strict equality) operators are used for comparisons in
JavaScript, but they behave differently:

● == (loose equality): Converts both values to the same type before comparing them.
This is known as type coercion.
● === (strict equality): Compares both the value and type without type conversion. If
the types are different, it immediately returns false.

For Example:

console.log(2 == "2"); // Output: true (type coercion: "2" is converted to 2)


console.log(2 === "2"); // Output: false (different types: number vs string)
console.log(null == undefined); // Output: true (special case of coercion)
console.log(null === undefined); // Output: false (different types)

Using === is generally recommended as it avoids unexpected type conversions.

14. What is the purpose of the this keyword in JavaScript?

Answer:
The this keyword refers to the context in which a function is called. Its value can vary
depending on how the function is invoked. In general:

● In a method, this refers to the object the method belongs to.


● In a regular function, this refers to the global object (or undefined in strict mode).
● In an arrow function, this is lexically inherited from the surrounding scope.

For Example:

const person = {
name: "Shardul",
greet: function () {
console.log(`Hello, ${this.name}`);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
732

}
};

person.greet(); // Output: Hello, Shardul

const greetFunc = person.greet;


greetFunc(); // Output: Hello, undefined (or error in strict mode)

In the example, calling greet() directly through the person object correctly binds this to
person. However, when the method is assigned to a variable, it loses its context.

15. What are template literals in JavaScript?

Answer:
Template literals (introduced in ES6) are string literals enclosed by backticks (`). They allow
for string interpolation, where expressions can be embedded directly within the string using
${}. Template literals also support multi-line strings without needing special characters.

For Example:

const name = "Shardul";


const age = 25;
console.log(`Hello, my name is ${name} and I am ${age} years old.`);

// Multi-line string
const message = `This is a
multi-line string.`;
console.log(message);

// Output:
// Hello, my name is Shardul and I am 25 years old.
// This is a
// multi-line string.

Template literals make it easy to build strings dynamically and format them neatly.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
733

16. What is destructuring in JavaScript?

Answer:
Destructuring is a syntax introduced in ES6 that allows unpacking values from arrays or
objects into separate variables. This makes it easier to extract specific elements or properties.

For Example:

// Array destructuring
const [a, b] = [1, 2];
console.log(a, b); // Output: 1 2

// Object destructuring
const person = { name: "Shardul", age: 25 };
const { name, age } = person;
console.log(name, age); // Output: Shardul 25

Destructuring makes the code more concise and readable.

17. What are spread and rest operators in JavaScript?

Answer:
The spread operator (...) allows you to expand an array or object into individual elements.
The rest operator (...) collects multiple elements into an array. Both operators provide
flexibility when working with arrays, objects, and functions.

For Example:

// Spread operator example


const arr1 = [1, 2];
const arr2 = [...arr1, 3, 4];
console.log(arr2); // Output: [1, 2, 3, 4]

// Rest operator example


function sum(...numbers) {
return numbers.reduce((acc, curr) => acc + curr, 0);
}
console.log(sum(1, 2, 3)); // Output: 6

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
734

The spread operator helps with copying or combining arrays, while the rest operator handles
dynamic arguments.

18. What is a higher-order function in JavaScript?

Answer:
A higher-order function is a function that either accepts another function as an argument
or returns a function. Higher-order functions enable powerful abstractions and are
commonly used in functional programming.

For Example:

function greet(name) {
return function (message) {
console.log(`${message}, ${name}`);
};
}

const greetShardul = greet("Shardul");


greetShardul("Hello"); // Output: Hello, Shardul

Here, greet returns a new function, demonstrating the concept of a higher-order function.

19. What are callbacks in JavaScript?

Answer:
A callback is a function that is passed as an argument to another function and executed after
the completion of some operation. Callbacks are frequently used in asynchronous
programming to handle tasks such as timers and network requests.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
735

function fetchData(callback) {
setTimeout(() => {
callback("Data received");
}, 1000);
}

fetchData((data) => console.log(data)); // Output: Data received

In the example, the callback function is executed after a delay, simulating an asynchronous
operation.

20. What is the difference between map(), filter(), and reduce()?

Answer:
The map(), filter(), and reduce() methods are used to process arrays:

● map(): Creates a new array by transforming each element.


● filter(): Creates a new array with elements that satisfy a condition.
● reduce(): Reduces the array to a single value by accumulating results.

For Example:

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

const doubled = numbers.map(n => n * 2); // [2, 4, 6, 8, 10]


const even = numbers.filter(n => n % 2 === 0); // [2, 4]
const sum = numbers.reduce((acc, curr) => acc + curr, 0); // 15

console.log(doubled, even, sum);

These methods provide a clean and functional way to manipulate arrays.

21. What is the purpose of async and await in JavaScript?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
736

Answer:
JavaScript introduced async and await in ES8 to simplify working with asynchronous code.
Before their introduction, developers used callbacks and promises to handle asynchronous
operations. These constructs could lead to complex code with multiple .then() and
.catch() calls, making the code harder to read and maintain.

● async: When a function is declared with the async keyword, it always returns a
promise, regardless of whether the function explicitly returns a value or a promise. If a
function returns a non-promise value, it is automatically wrapped in a resolved
promise.
● await: The await keyword can only be used inside an async function. It pauses the
function’s execution until the promise resolves or rejects. This makes asynchronous
code appear synchronous and easier to read.

For Example:

async function fetchData() {


try {
let response = await fetch('https://jsonplaceholder.typicode.com/posts');
let data = await response.json(); // Waits until response is parsed to JSON
console.log(data); // Logs the fetched data
} catch (error) {
console.error('Error fetching data:', error); // Handles errors
}
}

fetchData();

Here, the await keyword ensures the function waits for the network request and JSON
parsing to complete before continuing, improving readability by avoiding chained .then()
methods.

22. How does JavaScript handle memory management and garbage


collection?

Answer:
JavaScript automatically manages memory using a garbage collector that frees memory

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
737

occupied by objects that are no longer needed. This process ensures that memory leaks are
minimized. The JavaScript engine typically uses two algorithms:

1. Reference Counting:
In this method, the engine keeps track of how many references point to an object. If
the reference count drops to zero (i.e., no references exist), the object is eligible for
garbage collection.
2. Mark-and-Sweep:
This is the more commonly used algorithm. The garbage collector marks all objects
that can still be accessed from the global scope or other reachable objects. After
marking, it "sweeps" away unmarked objects from memory.

For Example:

let obj = { name: "Shardul" };


obj = null; // The object is now eligible for garbage collection

In this example, when obj is set to null, the original object is no longer referenced and can
be garbage-collected.

23. Explain event delegation in JavaScript.

Answer:
Event delegation is a technique that leverages event bubbling to manage events efficiently.
When an event occurs on an element, it first triggers on the target element, then bubbles up
to its parent and ancestors. By placing an event listener on a parent element, you can listen
for events occurring on its child elements without adding individual listeners to each child.

This is especially useful when elements are dynamically added or removed, such as buttons
or list items generated at runtime.

For Example:

document.getElementById("parent").addEventListener("click", function (event) {


if (event.target.tagName === "BUTTON") {
console.log(`Button ${event.target.textContent} clicked`);
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
738

});

Here, a single event listener on the parent element handles click events for all its buttons,
even those added dynamically.

24. What is the difference between shallow and deep cloning in


JavaScript?

Answer:
Shallow cloning copies only the top-level properties of an object, while any nested objects
are still referenced, meaning both the original and the clone share the same nested objects.
In contrast, deep cloning creates an entirely independent copy of the object, including all
nested structures.

For Example:

// Shallow clone
const obj1 = { a: 1, b: { c: 2 } };
const shallowClone = { ...obj1 };
shallowClone.b.c = 3;
console.log(obj1.b.c); // Output: 3 (shared reference)

// Deep clone
const deepClone = JSON.parse(JSON.stringify(obj1));
deepClone.b.c = 4;
console.log(obj1.b.c); // Output: 3 (independent copy)

With shallow cloning, modifying the nested object in the clone also affects the original
object. Deep cloning avoids this by creating a new nested object.

25. What are JavaScript generators, and how do they work?

Answer:
A generator is a special type of function in JavaScript that can pause and resume its
execution. It is defined using the function* syntax and uses the yield keyword to return

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
739

values incrementally. Generators are useful for creating sequences or managing


asynchronous flows.

Each time the generator function is called with next(), it resumes from where it last
stopped.

For Example:

function* generatorFunction() {
yield 1;
yield 2;
yield 3;
}

const generator = generatorFunction();


console.log(generator.next().value); // Output: 1
console.log(generator.next().value); // Output: 2
console.log(generator.next().value); // Output: 3

The yield keyword allows the function to pause execution and return a value, making
generators suitable for on-demand computations.

26. How do you implement memoization in JavaScript?

Answer:
Memoization is an optimization technique where the results of expensive function calls are
cached and returned when the same inputs occur again. This improves performance by
avoiding redundant calculations.

For Example:

function memoize(fn) {
const cache = {};
return function (...args) {
const key = JSON.stringify(args);
if (cache[key]) return cache[key];
const result = fn(...args);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
740

cache[key] = result;
return result;
};
}

const factorial = memoize(function (n) {


if (n === 0) return 1;
return n * factorial(n - 1);
});

console.log(factorial(5)); // Output: 120


console.log(factorial(5)); // Output: 120 (from cache)

In this example, subsequent calls with the same input use the cached result, improving
performance.

27. What is the module pattern in JavaScript?

Answer:
The module pattern is a design pattern used to encapsulate code within a function,
exposing only what is necessary through an API. This promotes data privacy and prevents
namespace pollution.

For Example:

const Counter = (function () {


let count = 0; // Private variable
return {
increment() {
count++;
console.log(count);
},
reset() {
count = 0;
console.log(count);
}
};
})();

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
741

Counter.increment(); // Output: 1
Counter.increment(); // Output: 2
Counter.reset(); // Output: 0

Here, the count variable is private, accessible only through the public methods.

28. How does debouncing work in JavaScript?

Answer:
Debouncing ensures that a function is called only once after a specified delay, regardless of
how many times it is triggered during that delay. It is often used to limit the frequency of
events like typing or scrolling.

For Example:

function debounce(func, delay) {


let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), delay);
};
}

const search = debounce((query) => {


console.log(`Searching for: ${query}`);
}, 300);

search("JavaScript");
search("JavaScript Debounce");

Only the last input triggers the search function after a 300ms delay, reducing unnecessary
calls.

29. How does throttling work in JavaScript?

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
742

Answer:
Throttling limits the number of times a function can be called within a specific period. It
ensures that the function executes at most once per interval, even if it is triggered multiple
times.

For Example:

function throttle(func, limit) {


let lastFunc;
let lastRan;
return function (...args) {
const context = this;
if (!lastRan) {
func.apply(context, args);
lastRan = Date.now();
} else {
clearTimeout(lastFunc);
lastFunc = setTimeout(function () {
if (Date.now() - lastRan >= limit) {
func.apply(context, args);
lastRan = Date.now();
}
}, limit - (Date.now() - lastRan));
}
};
}

const log = throttle(() => console.log("Scroll event"), 1000);


window.addEventListener("scroll", log);

In this example, the log function will run at most once every second, even if the scroll event
fires continuously.

30. What are weak references in JavaScript, and how are they used?

Answer:
A weak reference allows you to refer to an object without preventing it from being garbage-
collected. JavaScript provides WeakMap and WeakSet to handle weak references. These

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
743

structures only hold "weak" references to objects, meaning if an object is no longer


referenced elsewhere, it becomes eligible for garbage collection.

For Example:

let obj = { name: "Shardul" };


const weakMap = new WeakMap();
weakMap.set(obj, "Some value");

console.log(weakMap.has(obj)); // Output: true

obj = null; // The object is now eligible for garbage collection


console.log(weakMap.has(obj)); // Output: false

In this example, the object obj is removed from the WeakMap when no longer referenced,
helping prevent memory leaks.

31. What are Web Workers in JavaScript, and how do they improve
performance?

Answer:
Web Workers allow JavaScript to execute code in parallel threads, separate from the main
browser thread. This ensures that intensive tasks like complex calculations, data processing,
or image manipulation do not block the main thread, which handles UI rendering and user
interactions. Web Workers run in their own context and communicate with the main thread
through messages. However, since they operate in a different context, they do not have
access to the DOM or some browser APIs like window and document.

Types of Web Workers:

1. Dedicated Web Workers: Used by a single script.


2. Shared Web Workers: Can be used by multiple scripts running in different windows
or tabs.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
744

worker.js

// Worker thread code


self.onmessage = function (event) {
const result = event.data * 2; // Perform some heavy computation
postMessage(result); // Send result back to the main thread
};

main.js

const worker = new Worker('worker.js'); // Create a new worker

worker.postMessage(5); // Send data to the worker

worker.onmessage = function (event) {


console.log(`Result from worker: ${event.data}`); // Output: 10
};

This example creates a dedicated Web Worker that receives data, performs a calculation,
and sends the result back to the main thread. By doing this in the background, the UI
remains responsive.

32. What is the difference between localStorage, sessionStorage, and


cookies?

Answer:
These mechanisms store client-side data but differ in behavior and use cases.

1. localStorage:
○ Data persists even after the browser is closed.
○ Stores larger amounts of data (up to 5–10 MB).
○ Useful for storing preferences or offline data.
2. sessionStorage:
○ Data lasts only for the session (until the tab or browser is closed).
○ Typically used for temporary data, such as form inputs.
3. Cookies:
○ Sent with each HTTP request, allowing data exchange with the server.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
745

○ Size limited to 4 KB per cookie.


○ Can store sensitive session data, often with an expiration date.

For Example:

// Store and retrieve data with localStorage


localStorage.setItem('username', 'Shardul');
console.log(localStorage.getItem('username')); // Output: Shardul

// Store and retrieve data with sessionStorage


sessionStorage.setItem('token', 'abc123');
console.log(sessionStorage.getItem('token')); // Output: abc123

// Create and retrieve a cookie


document.cookie = "user=Shardul; expires=Fri, 31 Dec 2024 12:00:00 UTC";
console.log(document.cookie); // Output: user=Shardul

33. What is the Shadow DOM, and why is it used?

Answer:
The Shadow DOM is part of the Web Components standard, which allows developers to
encapsulate the structure, styles, and behavior of a component within a shadow tree. This
prevents CSS styles and JavaScript code from affecting elements outside the shadow DOM
and vice versa. It is useful when building reusable and isolated components.

For Example:

const shadowHost = document.getElementById('host');


const shadowRoot = shadowHost.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `
<style>
p { color: red; }
</style>
<p>Hello Shadow DOM</p>
`;

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
746

The styles inside the shadow DOM are scoped to that component, preventing them from
interfering with the global CSS.

34. What is the difference between call(), apply(), and bind()?

Answer:
call(), apply(), and bind() are methods used to explicitly set the value of this when
calling a function.

● call(): Calls a function with a specified this value and individual arguments.
● apply(): Similar to call(), but arguments are passed as an array.
● bind(): Returns a new function with this value bound to the specified object. It does
not invoke the function immediately.

For Example:

const person = { name: "Shardul" };

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

greet.call(person, "Hello"); // Output: Hello, Shardul


greet.apply(person, ["Hi"]); // Output: Hi, Shardul

const boundGreet = greet.bind(person, "Hey");


boundGreet(); // Output: Hey, Shardul

The key difference is that bind() creates a new function, while call() and apply() invoke
the function immediately.

35. How do you implement a polyfill in JavaScript?

Answer:
A polyfill is code that provides modern functionality on older browsers or environments that

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
747

do not support it. Polyfills allow developers to ensure their code works across various
browsers by adding missing features.

For Example:

if (!Array.prototype.map) {
Array.prototype.map = function (callback) {
const result = [];
for (let i = 0; i < this.length; i++) {
result.push(callback(this[i], i, this));
}
return result;
};
}

console.log([1, 2, 3].map(x => x * 2)); // Output: [2, 4, 6]

This example implements a polyfill for Array.prototype.map to ensure the method works on
older browsers.

36. What are Proxies in JavaScript, and how do they work?

Answer:
A Proxy is an object that wraps another object and intercepts fundamental operations like
property access, assignment, and method invocation. This allows developers to add custom
behavior to these operations.

For Example:

const target = { name: "Shardul", age: 25 };


const handler = {
get(obj, prop) {
return prop in obj ? obj[prop] : "Property not found";
}
};

const proxy = new Proxy(target, handler);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
748

console.log(proxy.name); // Output: Shardul


console.log(proxy.salary); // Output: Property not found

In this example, the proxy intercepts the property access and provides custom behavior for
missing properties.

37. How does the JavaScript event loop manage microtasks and
macrotasks?

Answer:
The event loop in JavaScript processes tasks in two phases:

1. Macrotasks: Includes operations like setTimeout(), setInterval(), and I/O


operations.
2. Microtasks: Includes promise callbacks and MutationObserver callbacks. Microtasks
are always executed before the next macrotask.

For Example:

console.log("Start");

setTimeout(() => console.log("Macrotask"), 0);


Promise.resolve().then(() => console.log("Microtask"));

console.log("End");

// Output:
// Start
// End
// Microtask
// Macrotask

Here, the microtask runs before the macrotask, even though both are scheduled at the same
time.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
749

38. What are JavaScript WeakMaps and WeakSets?

Answer:
WeakMap and WeakSet are similar to Map and Set but hold weak references to objects. This
means the referenced objects can be garbage-collected if no other references exist.

● WeakMap: Holds key-value pairs, where keys must be objects.


● WeakSet: Holds collections of objects, with no duplicates allowed.

For Example:

let obj = { name: "Shardul" };


const weakMap = new WeakMap();
weakMap.set(obj, "Some value");

console.log(weakMap.has(obj)); // Output: true

obj = null; // The object is eligible for garbage collection


console.log(weakMap.has(obj)); // Output: false

39. What is the purpose of Symbol in JavaScript?

Answer:
Symbols are unique and immutable data types introduced in ES6. They are used as object
property keys to avoid naming conflicts, especially in situations where multiple libraries or
modules interact.

For Example:

const sym1 = Symbol('id');


const sym2 = Symbol('id');

console.log(sym1 === sym2); // Output: false

const obj = { [sym1]: 1, [sym2]: 2 };


console.log(obj[sym1]); // Output: 1
console.log(obj[sym2]); // Output: 2

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
750

Symbols guarantee that each value is unique, even with the same description.

40. How do you implement a custom iterator in JavaScript?

Answer:
A custom iterator in JavaScript follows the [Symbol.iterator] protocol. It defines an object
with a next() method that returns { value, done }. Iterators allow objects to be used in
loops like for...of.

For Example:

const iterable = {
[Symbol.iterator]() {
let step = 0;
return {
next() {
step++;
if (step <= 3) {
return { value: step, done: false };
}
return { value: undefined, done: true };
}
};
}
};

for (const value of iterable) {


console.log(value); // Output: 1 2 3
}

This example defines a custom iterator that yields values from 1 to 3.

SCENARIO QUESTIONS

41. Scenario: Array Manipulation in JavaScript

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
751

Question: How would you remove duplicate elements from an array efficiently?
Answer:
To remove duplicate elements from an array, the most efficient way in modern JavaScript is
by using a Set. A Set is a collection that only allows unique values, and converting the array
to a set will automatically remove any duplicates. You can then convert the set back to an
array using the spread operator (...). This approach is both concise and efficient in terms of
time complexity.

For Example:

function removeDuplicates(arr) {
return [...new Set(arr)];
}

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


console.log(removeDuplicates(numbers)); // Output: [1, 2, 3, 4, 5]

The Set automatically removes duplicate elements, and the spread operator returns a new
array with only unique values. This solution has a time complexity of O(n), making it highly
efficient for large datasets.

42. Scenario: String Manipulation

Question: How can you reverse a string in JavaScript?


Answer:
In JavaScript, you can reverse a string by splitting it into an array of characters, reversing the
array, and then joining the array back into a string. This is a simple and effective way to
reverse a string.

For Example:

function reverseString(str) {
return str.split('').reverse().join('');
}

console.log(reverseString("JavaScript")); // Output: tpircSavaJ

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
752

The split() method converts the string into an array of characters, reverse() reverses the
array, and join() merges it back into a single string. This approach works well for typical
strings but may need adjustments for Unicode characters.

43. Scenario: Recursion Problem

Question: Write a recursive function to find the factorial of a number.


Answer:
A recursive function calls itself until a base case is met. To calculate the factorial of a
number, the function multiplies the number by the factorial of the number minus one. This
continues until the base case, which is n === 0.

For Example:

function factorial(n) {
if (n === 0) return 1; // Base case
return n * factorial(n - 1); // Recursive call
}

console.log(factorial(5)); // Output: 120

This function calculates the factorial of 5 as 5 * 4 * 3 * 2 * 1, which equals 120.

44. Scenario: Sorting Algorithms

Question: How would you implement Bubble Sort in JavaScript?


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

For Example:

function bubbleSort(arr) {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
753

let n = arr.length;
for (let i = 0; i < n - 1; i++) {
for (let j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]; // Swap
}
}
}
return arr;
}

console.log(bubbleSort([5, 3, 8, 4, 2])); // Output: [2, 3, 4, 5, 8]

The time complexity of Bubble Sort is O(n²), making it less efficient for large datasets.

45. Scenario: Search Algorithms

Question: How would you implement Binary Search in JavaScript?


Answer:
Binary Search is an efficient algorithm for finding an element in a sorted array. It works by
repeatedly dividing the search interval in half until the target value is found or the search
interval is empty. The array must be sorted for Binary Search to work.

For Example:

function binarySearch(arr, target) {


let left = 0;
let right = arr.length - 1;

while (left <= right) {


const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) return mid;
else if (arr[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1; // Element not found
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
754

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

Binary Search has a time complexity of O(log n), making it very efficient.

46. Scenario: Stack Data Structure

Question: How would you implement a Stack using JavaScript?


Answer:
A stack is a data structure that follows the LIFO (Last In, First Out) principle. You can
implement a stack in JavaScript using an array with methods for push (add) and pop
(remove).

For Example:

class Stack {
constructor() {
this.items = [];
}

push(element) {
this.items.push(element);
}

pop() {
if (this.items.length === 0) return "Underflow";
return this.items.pop();
}

peek() {
return this.items[this.items.length - 1];
}
}

const stack = new Stack();


stack.push(1);
stack.push(2);
console.log(stack.peek()); // Output: 2
stack.pop();

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
755

console.log(stack.peek()); // Output: 1

This implementation supports push, pop, and peek operations on the stack.

47. Scenario: Queue Data Structure

Question: How would you implement a Queue using JavaScript?


Answer:
A queue follows the FIFO (First In, First Out) principle. It can be implemented using an array
in JavaScript, with enqueue to add elements and dequeue to remove elements from the front.

For Example:

class Queue {
constructor() {
this.items = [];
}

enqueue(element) {
this.items.push(element);
}

dequeue() {
if (this.items.length === 0) return "Underflow";
return this.items.shift();
}

front() {
return this.items[0];
}
}

const queue = new Queue();


queue.enqueue(1);
queue.enqueue(2);
console.log(queue.front()); // Output: 1
queue.dequeue();
console.log(queue.front()); // Output: 2

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
756

This queue implementation provides enqueue, dequeue, and front operations.

48. Scenario: Designing a Tree Data Structure

Question: How would you implement a Binary Tree in JavaScript?


Answer:
A Binary Tree is a tree structure in which each node has at most two children. You can
implement it by defining a Node class and a Tree class.

For Example:

class Node {
constructor(data) {
this.data = data;
this.left = null;
this.right = null;
}
}

class BinaryTree {
constructor() {
this.root = null;
}

insert(data) {
const newNode = new Node(data);
if (this.root === null) {
this.root = newNode;
} else {
this.insertNode(this.root, newNode);
}
}

insertNode(node, newNode) {
if (newNode.data < node.data) {
if (node.left === null) node.left = newNode;

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
757

else this.insertNode(node.left, newNode);


} else {
if (node.right === null) node.right = newNode;
else this.insertNode(node.right, newNode);
}
}
}

const tree = new BinaryTree();


tree.insert(5);
tree.insert(3);
tree.insert(7);
console.log(tree);

This code creates a binary tree with insertion logic based on data comparison.

49. Scenario: Frontend-heavy Application Design

Question: How would you optimize a React-based frontend-heavy application?


Answer:
When designing frontend-heavy applications in React, performance optimization is crucial.
You can:

● Use memoization with React.memo to avoid unnecessary re-renders.


● Implement code splitting using React’s lazy() and Suspense.
● Optimize state management with Context API or Redux.
● Use debouncing for input-heavy components.

For Example:

import React, { useState, useMemo } from 'react';

function ExpensiveComponent({ numbers }) {


const sum = useMemo(() => {
return numbers.reduce((acc, curr) => acc + curr, 0);
}, [numbers]);

return <div>Sum: {sum}</div>;

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
758

In this example, useMemo prevents recalculating the sum unless the numbers array changes.

50. Scenario: API Design with Express and Node.js

Question: How would you design a basic RESTful API using Express?
Answer:
To design a RESTful API with Express, you define routes for different CRUD operations. Use
HTTP methods like GET, POST, PUT, and DELETE to perform operations on resources.

For Example:

const express = require('express');


const app = express();
app.use(express.json());

let users = [{ id: 1, name: "Shardul" }];

app.get('/users', (req, res) => res.json(users));

app.post('/users', (req, res) => {


const user = req.body;
users.push(user);
res.status(201).json(user);
});

app.listen(3000, () => console.log('Server running on port 3000'));

This API provides endpoints to retrieve and create users, following RESTful principles.

51. Scenario: Handling Asynchronous Operations

Question: How can you use Promise.all() to handle multiple promises in JavaScript?
Answer:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
759

Promise.all() is used to execute multiple promises in parallel and waits for all of them to
resolve. It returns a single promise that resolves when all input promises resolve, or rejects if
any one of the promises fails. This method ensures that you can work with the results of
multiple asynchronous operations together. It’s especially useful for fetching data from
multiple APIs or executing independent tasks that need to complete before proceeding.

For Example:

const promise1 = fetch('https://jsonplaceholder.typicode.com/posts/1');


const promise2 = fetch('https://jsonplaceholder.typicode.com/posts/2');

Promise.all([promise1, promise2])
.then(responses => Promise.all(responses.map(res => res.json())))
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

Explanation:

1. Both fetch calls are initiated simultaneously.


2. Promise.all() waits for both promises to resolve.
3. If one promise fails, the entire Promise.all() fails, and the catch block handles the
error.
4. Promise.all() ensures all results are available at once, making it ideal for concurrent
requests.

52. Scenario: Error Handling in Asynchronous Code

Question: How would you handle errors using async and await in JavaScript?
Answer:
Using async and await simplifies working with asynchronous operations. With try-catch
blocks, errors are handled just like synchronous code. This approach improves readability
and makes it easier to track errors compared to promise chains.

For Example:

async function fetchData(url) {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
760

try {
const response = await fetch(url);
if (!response.ok) throw new Error('Network response was not ok');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Error:', error.message);
}
}

fetchData('https://jsonplaceholder.typicode.com/posts/1');

Explanation:

1. await pauses the function execution until the fetch promise resolves.
2. If the fetch fails or the server returns an error, the code throws an exception.
3. The catch block handles any errors, ensuring graceful failure handling.

53. Scenario: Debouncing User Input

Question: How would you implement debouncing to improve search functionality?


Answer:
Debouncing ensures that a function is called only once after a certain delay, even if it is
triggered multiple times within that period. This is crucial for search inputs, as it prevents
excessive API calls while the user is still typing.

For Example:

function debounce(func, delay) {


let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), delay);
};
}

const search = debounce((query) => {


console.log(`Searching for: ${query}`);

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
761

}, 300);

search('JavaScript');
search('JavaScript Debounce'); // Only this call will execute after 300ms

Explanation:

1. Every call to search resets the timeout, ensuring only the last call is executed.
2. This reduces the number of API calls and improves performance.

54. Scenario: Throttling Scroll Events

Question: How would you implement throttling to optimize scroll event handling?
Answer:
Throttling ensures that a function executes at most once within a specified time, even if the
event is triggered repeatedly. This is essential for handling events like scrolling or resizing
efficiently.

For Example:

function throttle(func, limit) {


let lastFunc;
let lastRan;
return function (...args) {
const context = this;
if (!lastRan) {
func.apply(context, args);
lastRan = Date.now();
} else {
clearTimeout(lastFunc);
lastFunc = setTimeout(() => {
if (Date.now() - lastRan >= limit) {
func.apply(context, args);
lastRan = Date.now();
}
}, limit - (Date.now() - lastRan));
}
};

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
762

const logScroll = throttle(() => console.log('Scrolled!'), 1000);


window.addEventListener('scroll', logScroll);

Explanation:

1. This code ensures the logScroll function runs at most once per second.
2. The throttling technique optimizes performance by limiting event-triggered calls.

55. Scenario: Using the Spread Operator

Question: How can you use the spread operator to clone and merge objects?
Answer:
The spread operator (...) allows you to clone objects or merge multiple objects into a new
one. This approach simplifies working with objects and ensures immutability.

For Example:

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

const clonedObj = { ...obj1 };


const mergedObj = { ...obj1, ...obj2 };

console.log(clonedObj); // Output: { a: 1, b: 2 }
console.log(mergedObj); // Output: { a: 1, b: 3, c: 4 }

Explanation:

1. Cloning: The spread operator creates a shallow copy of the object.


2. Merging: Conflicting properties are overwritten by the later object’s values.

56. Scenario: Using the Rest Operator

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
763

Question: How does the rest operator simplify function arguments handling?
Answer:
The rest operator (...) collects multiple arguments into an array. This makes it easy to create
functions that can handle variable numbers of arguments.

For Example:

function sum(...numbers) {
return numbers.reduce((acc, num) => acc + num, 0);
}

console.log(sum(1, 2, 3)); // Output: 6


console.log(sum(4, 5, 6, 7)); // Output: 22

Explanation:

1. The rest operator collects all function arguments into a single array.
2. This makes the function flexible and reusable.

57. Scenario: Using Map in JavaScript

Question: How would you use a Map to store key-value pairs in JavaScript?
Answer:
A Map allows you to store key-value pairs, with the keys being of any type, including objects
or functions. It provides useful methods like set(), get(), has(), and delete().

For Example:

const map = new Map();


map.set('name', 'Shardul');
map.set('age', 25);

console.log(map.get('name')); // Output: Shardul


console.log(map.has('age')); // Output: true
map.delete('age');
console.log(map.size); // Output: 1

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
764

Explanation:

1. Maps are more versatile than objects, as keys can be non-string values.
2. The size property directly returns the number of entries.

58. Scenario: Implementing a Set

Question: How would you use a Set to store unique values in JavaScript?
Answer:
A Set ensures that only unique values are stored. It is useful when you need to eliminate
duplicates or manage collections of distinct elements.

For Example:

const set = new Set([1, 2, 3, 3, 4]);


console.log(set); // Output: Set(4) { 1, 2, 3, 4 }

set.add(5);
set.delete(2);
console.log(set.has(2)); // Output: false
console.log(set.size); // Output: 4

Explanation:

1. Sets automatically remove duplicate values.


2. The size property returns the number of unique elements in the set.

59. Scenario: Custom Iterator with Symbol.iterator

Question: How would you create a custom iterator in JavaScript?


Answer:
A custom iterator allows an object to be iterated over using a for...of loop. This requires
implementing the [Symbol.iterator] method.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
765

const iterable = {
[Symbol.iterator]() {
let step = 0;
return {
next() {
step++;
if (step <= 3) return { value: step, done: false };
return { value: undefined, done: true };
}
};
}
};

for (const value of iterable) {


console.log(value); // Output: 1 2 3
}

Explanation:

1. The next() method returns an object with value and done properties.
2. This approach enables custom iteration logic.

60. Scenario: Using WeakMap for Garbage Collection

Question: How does WeakMap ensure efficient garbage collection in JavaScript?


Answer:
A WeakMap holds weak references to its keys, meaning if there are no other references to a
key, it becomes eligible for garbage collection. This prevents memory leaks.

For Example:

let obj = { name: 'Shardul' };


const weakMap = new WeakMap();
weakMap.set(obj, 'some value');

console.log(weakMap.has(obj)); // Output: true

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
766

obj = null; // The object is now eligible for garbage collection


console.log(weakMap.has(obj)); // Output: false

Explanation:

1. The WeakMap entry is automatically removed when the object is no longer referenced.
2. This ensures efficient memory management.

61. Scenario: Implementing Memoization

Question: How would you implement memoization to optimize a recursive function?


Answer:
Memoization is an optimization technique where you store the results of expensive function
calls and return the cached result when the same inputs occur again. This is particularly
useful in recursive functions that perform redundant calculations, like the Fibonacci
sequence.

For Example:

function memoize(fn) {
const cache = {};
return function (...args) {
const key = JSON.stringify(args);
if (cache[key]) return cache[key];
const result = fn(...args);
cache[key] = result;
return result;
};
}

const fibonacci = memoize(function (n) {


if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
});

console.log(fibonacci(10)); // Output: 55

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
767

Explanation:

1. The memoize function caches previous results to avoid redundant recursive calls.
2. This improves the time complexity of recursive functions from O(2^n) to O(n).

62. Scenario: Designing a Graph Data Structure

Question: How would you implement a graph using adjacency lists in JavaScript?
Answer:
A graph can be represented using an adjacency list, where each node points to a list of
connected nodes. This is an efficient way to store sparse graphs.

For Example:

class Graph {
constructor() {
this.adjacencyList = {};
}

addVertex(vertex) {
if (!this.adjacencyList[vertex]) this.adjacencyList[vertex] = [];
}

addEdge(vertex1, vertex2) {
this.adjacencyList[vertex1].push(vertex2);
this.adjacencyList[vertex2].push(vertex1);
}
}

const graph = new Graph();


graph.addVertex('A');
graph.addVertex('B');
graph.addEdge('A', 'B');
console.log(graph.adjacencyList); // Output: { A: [ 'B' ], B: [ 'A' ] }

Explanation:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
768

1. This graph implementation supports adding vertices and edges.


2. The adjacency list ensures efficient storage, especially for sparse graphs.

63. Scenario: Implementing Depth-First Search (DFS)

Question: How would you implement Depth-First Search (DFS) in a graph?


Answer:
DFS explores a graph by traversing as deep as possible along a branch before backtracking.
It is typically implemented using recursion or a stack.

For Example:

class Graph {
constructor() {
this.adjacencyList = {};
}

addVertex(vertex) {
if (!this.adjacencyList[vertex]) this.adjacencyList[vertex] = [];
}

addEdge(vertex1, vertex2) {
this.adjacencyList[vertex1].push(vertex2);
this.adjacencyList[vertex2].push(vertex1);
}

dfs(start, visited = new Set()) {


console.log(start);
visited.add(start);
for (let neighbor of this.adjacencyList[start]) {
if (!visited.has(neighbor)) {
this.dfs(neighbor, visited);
}
}
}
}

const graph = new Graph();


graph.addVertex('A');
graph.addVertex('B');

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
769

graph.addVertex('C');
graph.addEdge('A', 'B');
graph.addEdge('A', 'C');
graph.dfs('A'); // Output: A B C

Explanation:

1. DFS explores all neighbors of a vertex before moving deeper.


2. The recursion ensures that the graph is traversed completely.

64. Scenario: Implementing Breadth-First Search (BFS)

Question: How would you implement Breadth-First Search (BFS) in JavaScript?


Answer:
BFS explores all neighbors of a node before moving to the next level of neighbors. It uses a
queue to manage the traversal.

For Example:

class Graph {
constructor() {
this.adjacencyList = {};
}

addVertex(vertex) {
if (!this.adjacencyList[vertex]) this.adjacencyList[vertex] = [];
}

addEdge(vertex1, vertex2) {
this.adjacencyList[vertex1].push(vertex2);
this.adjacencyList[vertex2].push(vertex1);
}

bfs(start) {
const queue = [start];
const visited = new Set(queue);

while (queue.length > 0) {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
770

const vertex = queue.shift();


console.log(vertex);

for (let neighbor of this.adjacencyList[vertex]) {


if (!visited.has(neighbor)) {
visited.add(neighbor);
queue.push(neighbor);
}
}
}
}
}

const graph = new Graph();


graph.addVertex('A');
graph.addVertex('B');
graph.addVertex('C');
graph.addEdge('A', 'B');
graph.addEdge('A', 'C');
graph.bfs('A'); // Output: A B C

Explanation:

1. BFS ensures that all vertices at the same depth are explored before going deeper.
2. The queue guarantees the correct traversal order.

65. Scenario: Designing a Priority Queue

Question: How would you implement a Priority Queue in JavaScript?


Answer:
A priority queue is a data structure where elements are dequeued based on their priority
rather than their insertion order.

For Example:

class PriorityQueue {
constructor() {
this.items = [];

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
771

enqueue(element, priority) {
const queueElement = { element, priority };
let added = false;

for (let i = 0; i < this.items.length; i++) {


if (this.items[i].priority > priority) {
this.items.splice(i, 0, queueElement);
added = true;
break;
}
}

if (!added) this.items.push(queueElement);
}

dequeue() {
return this.items.shift();
}
}

const pq = new PriorityQueue();


pq.enqueue('A', 2);
pq.enqueue('B', 1);
console.log(pq.dequeue()); // Output: { element: 'B', priority: 1 }

Explanation:

1. Elements with higher priority are dequeued before lower priority ones.
2. The queue maintains an ordered insertion based on priority.

66. Scenario: Implementing Merge Sort

Question: How would you implement Merge Sort in JavaScript?


Answer:
Merge Sort is a divide-and-conquer algorithm that splits the array into smaller parts, sorts
them, and then merges them back.

For Example:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
772

function mergeSort(arr) {
if (arr.length <= 1) return arr;

const mid = Math.floor(arr.length / 2);


const left = mergeSort(arr.slice(0, mid));
const right = mergeSort(arr.slice(mid));

return merge(left, right);


}

function merge(left, right) {


const result = [];
while (left.length && right.length) {
if (left[0] < right[0]) result.push(left.shift());
else result.push(right.shift());
}
return [...result, ...left, ...right];
}

console.log(mergeSort([5, 3, 8, 4, 2])); // Output: [2, 3, 4, 5, 8]

Explanation:

1. Merge Sort recursively divides the array and sorts it.


2. Its time complexity is O(n log n).

67. Scenario: API Rate Limiting Design

Question: How would you implement API rate limiting in Node.js?


Answer:
API rate limiting controls the number of requests a user can make within a specified time
window. This helps prevent overloading the server.

For Example:

const express = require('express');


const rateLimit = require('express-rate-limit');

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
773

const app = express();

const limiter = rateLimit({


windowMs: 1 * 60 * 1000, // 1 minute
max: 5, // Limit each IP to 5 requests per minute
message: 'Too many requests, please try again later.',
});

app.use(limiter);

app.get('/', (req, res) => {


res.send('Welcome to the API');
});

app.listen(3000, () => console.log('Server running on port 3000'));

Explanation:

1. The express-rate-limit middleware limits requests.


2. Each IP can make 5 requests per minute.

68. Scenario: Implementing Quick Sort

Question: How would you implement Quick Sort in JavaScript?


Answer:
Quick Sort is another divide-and-conquer algorithm that picks a pivot and partitions the
array around it.

For Example:

function quickSort(arr) {
if (arr.length <= 1) return arr;

const pivot = arr[arr.length - 1];


const left = arr.filter(el => el < pivot);
const right = arr.filter(el => el > pivot);

return [...quickSort(left), pivot, ...quickSort(right)];


}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
774

console.log(quickSort([5, 3, 8, 4, 2])); // Output: [2, 3, 4, 5, 8]

69. Scenario: Designing an LRU Cache

Question: How would you implement an LRU (Least Recently Used) cache in JavaScript?
Answer:
An LRU cache keeps the most recently accessed elements in memory and evicts the least
recently used ones.

For Example:

class LRUCache {
constructor(limit) {
this.cache = new Map();
this.limit = limit;
}

get(key) {
if (!this.cache.has(key)) return -1;
const value = this.cache.get(key);
this.cache.delete(key);
this.cache.set(key, value);
return value;
}

put(key, value) {
if (this.cache.has(key)) this.cache.delete(key);
else if (this.cache.size === this.limit)
this.cache.delete(this.cache.keys().next().value);
this.cache.set(key, value);
}
}

const lru = new LRUCache(2);


lru.put(1, 'A');

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
775

lru.put(2, 'B');
console.log(lru.get(1)); // Output: A
lru.put(3, 'C');
console.log(lru.get(2)); // Output: -1

Explanation:

1. The cache maintains the most recent keys using a Map.


2. When the limit is reached, the least used entry is removed.

70. Scenario: Securing Node.js API with JWT

Question: How would you implement JWT authentication in a Node.js API?


Answer:
JWT (JSON Web Token) is a popular method for securing APIs by passing encrypted user
information.

For Example:

const express = require('express');


const jwt = require('jsonwebtoken');
const app = express();

const SECRET_KEY = 'your-secret-key';

app.post('/login', (req, res) => {


const token = jwt.sign({ user: 'Shardul' }, SECRET_KEY, { expiresIn: '1h' });
res.json({ token });
});

app.get('/protected', (req, res) => {


const token = req.headers['authorization'];
if (!token) return res.status(401).send('Access denied.');

try {
jwt.verify(token, SECRET_KEY);
res.send('Protected resource accessed.');

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
776

} catch (err) {
res.status(401).send('Invalid token.');
}
});

app.listen(3000, () => console.log('Server running on port 3000'));

Explanation:

1. Users log in to receive a JWT token.


2. The token is used to access protected routes.

71. Scenario: Implementing Memoization

Question: How would you implement memoization to optimize a recursive function?


Answer:
Memoization is a performance optimization technique that stores the results of expensive
function calls and returns the cached result when the same inputs occur again. It is
particularly useful in recursive functions where the same calculations are performed
repeatedly, such as calculating Fibonacci numbers.

When you call a recursive function, the function may call itself with the same arguments
multiple times, leading to redundant calculations. By storing previously computed results,
you can significantly reduce the time complexity.

For Example:

function memoize(fn) {
const cache = {};
return function (...args) {
const key = JSON.stringify(args); // Create a unique key for the arguments
if (cache[key]) return cache[key]; // Return cached result if it exists
const result = fn(...args); // Compute result
cache[key] = result; // Cache the result
return result; // Return the result
};

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
777

const fibonacci = memoize(function (n) {


if (n <= 1) return n; // Base case
return fibonacci(n - 1) + fibonacci(n - 2); // Recursive case
});

console.log(fibonacci(10)); // Output: 55

Explanation:

1. The memoize function creates a cache to store previously computed results.


2. The cache uses a unique key for each set of arguments, ensuring that results can be
retrieved quickly.
3. When calculating the Fibonacci of 10, it utilizes cached results, leading to O(n) time
complexity instead of O(2^n).

72. Scenario: Designing a Graph Data Structure

Question: How would you implement a graph using adjacency lists in JavaScript?
Answer:
A graph can be represented using an adjacency list, where each vertex (node) points to a list
of its connected vertices. This method is efficient in terms of both space and performance,
especially for sparse graphs.

For Example:

class Graph {
constructor() {
this.adjacencyList = {}; // Initialize an empty adjacency list
}

addVertex(vertex) {
if (!this.adjacencyList[vertex]) this.adjacencyList[vertex] = []; // Create
an array for new vertex
}

addEdge(vertex1, vertex2) {

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
778

this.adjacencyList[vertex1].push(vertex2); // Add edge from vertex1 to


vertex2
this.adjacencyList[vertex2].push(vertex1); // Add edge from vertex2 to
vertex1 (for undirected graph)
}
}

const graph = new Graph();


graph.addVertex('A');
graph.addVertex('B');
graph.addEdge('A', 'B');
console.log(graph.adjacencyList); // Output: { A: [ 'B' ], B: [ 'A' ] }

Explanation:

1. The adjacency list is implemented as an object where each key represents a vertex
and its value is an array of adjacent vertices.
2. This structure allows efficient addition of vertices and edges.
3. It is space-efficient, particularly for sparse graphs, as it only stores existing
connections.

73. Scenario: Implementing Depth-First Search (DFS)

Question: How would you implement Depth-First Search (DFS) in a graph?


Answer:
Depth-First Search (DFS) is an algorithm for traversing or searching through a graph. It
explores as far down one branch as possible before backtracking. This can be implemented
using recursion or an explicit stack.

For Example:

class Graph {
constructor() {
this.adjacencyList = {}; // Initialize an empty adjacency list
}

addVertex(vertex) {
if (!this.adjacencyList[vertex]) this.adjacencyList[vertex] = []; // Create

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
779

an array for new vertex


}

addEdge(vertex1, vertex2) {
this.adjacencyList[vertex1].push(vertex2); // Add edge from vertex1 to
vertex2
this.adjacencyList[vertex2].push(vertex1); // Add edge from vertex2 to
vertex1
}

dfs(start, visited = new Set()) {


console.log(start); // Visit the current node
visited.add(start); // Mark the node as visited

for (let neighbor of this.adjacencyList[start]) {


if (!visited.has(neighbor)) {
this.dfs(neighbor, visited); // Recursively visit unvisited
neighbors
}
}
}
}

const graph = new Graph();


graph.addVertex('A');
graph.addVertex('B');
graph.addVertex('C');
graph.addEdge('A', 'B');
graph.addEdge('A', 'C');
graph.dfs('A'); // Output: A B C (or A C B, depending on the order of edges)

Explanation:

1. The dfs method uses a set to keep track of visited nodes.


2. It prints each node as it visits, allowing you to see the traversal order.
3. The recursive approach simplifies the code and allows for straightforward traversal of
the graph.

74. Scenario: Implementing Breadth-First Search (BFS)

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
780

Question: How would you implement Breadth-First Search (BFS) in JavaScript?


Answer:
Breadth-First Search (BFS) is an algorithm for traversing a graph level by level, using a
queue to manage the nodes to be explored.

For Example:

class Graph {
constructor() {
this.adjacencyList = {}; // Initialize an empty adjacency list
}

addVertex(vertex) {
if (!this.adjacencyList[vertex]) this.adjacencyList[vertex] = []; // Create
an array for new vertex
}

addEdge(vertex1, vertex2) {
this.adjacencyList[vertex1].push(vertex2); // Add edge from vertex1 to
vertex2
this.adjacencyList[vertex2].push(vertex1); // Add edge from vertex2 to
vertex1
}

bfs(start) {
const queue = [start]; // Initialize the queue with the starting node
const visited = new Set(queue); // Mark the starting node as visited

while (queue.length > 0) {


const vertex = queue.shift(); // Dequeue the next vertex
console.log(vertex); // Visit the current node

for (let neighbor of this.adjacencyList[vertex]) {


if (!visited.has(neighbor)) {
visited.add(neighbor); // Mark neighbor as visited
queue.push(neighbor); // Enqueue unvisited neighbors
}
}
}
}
}

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
781

const graph = new Graph();


graph.addVertex('A');
graph.addVertex('B');
graph.addVertex('C');
graph.addEdge('A', 'B');
graph.addEdge('A', 'C');
graph.bfs('A'); // Output: A B C

Explanation:

1. The BFS algorithm uses a queue to manage nodes that need to be visited.
2. Each node is dequeued, and its neighbors are added to the queue if they haven't
been visited.
3. This guarantees that nodes are processed level by level.

75. Scenario: Designing a Priority Queue

Question: How would you implement a priority queue in JavaScript?


Answer:
A priority queue stores elements based on their priority rather than their order of insertion.
Higher priority elements are dequeued before lower priority ones.

For Example:

class PriorityQueue {
constructor() {
this.items = []; // Array to hold the elements
}

enqueue(element, priority) {
const queueElement = { element, priority }; // Create an object for the
element and its priority
let added = false;

for (let i = 0; i < this.items.length; i++) {


if (this.items[i].priority > priority) {
this.items.splice(i, 0, queueElement); // Insert in the correct
position

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
782

added = true;
break;
}
}

if (!added) this.items.push(queueElement); // If not added, push to the end


}

dequeue() {
return this.items.shift(); // Remove and return the highest priority
element
}
}

const pq = new PriorityQueue();


pq.enqueue('A', 2);
pq.enqueue('B', 1);
console.log(pq.dequeue()); // Output: { element: 'B', priority: 1 }

Explanation:

1. The enqueue method adds elements to the queue based on their priority.
2. Lower numerical priority values are dequeued first.
3. This structure efficiently handles dynamic priority-based requests.

76. Scenario: Implementing Merge Sort

Question: How would you implement Merge Sort in JavaScript?


Answer:
Merge Sort is a divide-and-conquer algorithm that splits an array into halves, sorts each half,
and then merges the sorted halves back together.

For Example:

function mergeSort(arr) {
if (arr.length <= 1) return arr; // Base case

const mid = Math.floor(arr.length / 2); // Find the midpoint

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
783

const left = mergeSort(arr.slice(0, mid)); // Recursively sort the left half


const right = mergeSort(arr.slice(mid)); // Recursively sort the right half

return merge(left, right); // Merge the sorted halves


}

function merge(left, right) {


const result = [];
while (left.length && right.length) {
if (left[0] < right[0]) result.push(left.shift()); // Push the smaller
element
else result.push(right.shift()); // Push the smaller element
}
return [...result, ...left, ...right]; // Concatenate the remaining elements
}

console.log(mergeSort([5, 3, 8, 4, 2])); // Output: [2, 3, 4, 5, 8]

Explanation:

1. The array is recursively divided until single elements remain.


2. The merge function combines the sorted halves, ensuring order.
3. The time complexity of Merge Sort is O(n log n).

77. Scenario: API Rate Limiting Design

Question: How would you implement API rate limiting in Node.js?


Answer:
Rate limiting controls how often a user can make requests to an API, protecting against
abuse and ensuring fair resource usage. This can be implemented using middleware.

For Example:

const express = require('express');


const rateLimit = require('express-rate-limit');
const app = express();

const limiter = rateLimit({

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
784

windowMs: 1 * 60 * 1000, // 1 minute


max: 5, // Limit each IP to 5 requests per minute
message: 'Too many requests, please try again later.',
});

app.use(limiter);

app.get('/api/data', (req, res) => {


res.send('Here is your data.');
});

app.listen(3000, () => console.log('Server running on port 3000'));

Explanation:

1. The rate limiter limits requests from each IP to 5 requests per minute.
2. It sends an error message if the limit is exceeded, helping prevent abuse.

78. Scenario: Implementing GraphQL API

Question: How would you create a GraphQL API using Node.js?


Answer:
GraphQL is a powerful query language that allows clients to request specific data. It provides
flexibility in fetching data, unlike REST APIs.

For Example:

const express = require('express');


const { graphqlHTTP } = require('express-graphql');
const { buildSchema } = require('graphql');

const schema = buildSchema(`


type Query {
hello: String
}
`);

const root = { hello: () => 'Hello, GraphQL!' };

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
785

const app = express();


app.use('/graphql', graphqlHTTP({ schema, rootValue: root, graphiql: true }));

app.listen(3000, () => console.log('GraphQL API running on port 3000'));

Explanation:

1. The schema defines the structure of queries available.


2. The server listens for GraphQL requests and returns results based on defined
resolvers.

79. Scenario: Handling Multiple Database Connections

Question: How would you manage multiple database connections in Node.js?


Answer:
To manage multiple database connections, you can use connection pools to improve
performance by reusing connections rather than opening new ones for each request.

For Example:

const mysql = require('mysql2');

const pool1 = mysql.createPool({ host: 'db1', user: 'root', database: 'db1' });
const pool2 = mysql.createPool({ host: 'db2', user: 'root', database: 'db2' });

pool1.query('SELECT * FROM users', (err, results) => {


if (err) throw err;
console.log('DB1 Users:', results);
});

pool2.query('SELECT * FROM orders', (err, results) => {


if (err) throw err;
console.log('DB2 Orders:', results);
});

Explanation:

INTERVIEWNINJA.IN ECOMNOWVENTURESTM
786

1. Each database has its own connection pool for efficient management.
2. Queries are executed concurrently across different databases.

80. Scenario: Implementing File Uploads with Multer

Question: How would you implement file uploads using Multer in Node.js?
Answer:
Multer is middleware for handling multipart/form-data, which is used for file uploads in
forms. It simplifies file handling in Express.

For Example:

const express = require('express');


const multer = require('multer');
const upload = multer({ dest: 'uploads/' });
const app = express();

app.post('/upload', upload.single('file'), (req, res) => {


res.send('File uploaded successfully!');
});

app.listen(3000, () => console.log('Server running on port 3000'));

Explanation:

1. Multer saves uploaded files to the uploads directory.


2. The upload.single() method handles a single file upload from the form.

INTERVIEWNINJA.IN ECOMNOWVENTURESTM

You might also like