Unit3 - Subjective Questions
INT219 • Practice Questions with Detailed Answers
Explain the concept of Execution Context in JavaScript and describe its two main phases.
The Execution Context is the environment in which JavaScript code is executed. It contains information about the variables, objects, and the this keyword available at a specific time.
There are three types: Global, Function, and Eval execution contexts.
The creation of an Execution Context happens in two phases:
- Creation Phase (Memory Creation):
- Window Object &
this: The global object andthisvariable are created. - Hoisting: Memory is allocated for variables and functions. Variables declared with
varare initialized toundefined, while function declarations are stored fully in memory.
- Window Object &
- Execution Phase (Code Execution):
- The code is executed line by line.
- Values are assigned to the variables allocated in the previous phase.
- Function calls trigger the creation of new functional execution contexts.
Describe the Scope Chain mechanism and how JavaScript resolves variable access in nested functions.
Scope Chain is the mechanism JavaScript uses to resolve variable values in nested functions. It consists of the current function's variable object, the variable object of its parent (lexical) environment, and so on, up to the global scope.
How it works:
- Local Search: When a variable is accessed, the engine first looks in the Current Scope.
- Outer Scope: If not found, it moves up to the Outer (Parent) Scope.
- Global Scope: This continues until the Global Scope is reached.
- Reference Error: If the variable is not found in the global scope, a
ReferenceErroris thrown.
This behavior is determined by Lexical Scoping, meaning the scope is defined by the physical location of the code within the source file.
Define Closures in JavaScript. Provide a code example demonstrating how a closure preserves data privacy.
A Closure is a feature in JavaScript where an inner function has access to the outer (enclosing) function's variables—a scope chain. The closure has three scope chains: it has access to its own scope, the outer function's variables, and the global variables. Crucially, the inner function remembers the environment in which it was created, even after the outer function has finished executing.
Example: Data Privacy
javascript
function createCounter() {
let count = 0; // Private variable
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // Output: 1
console.log(counter()); // Output: 2
// 'count' cannot be accessed directly from here
In this example, count is hidden from the global scope but accessible to the anonymous returned function.
Differentiate between var, let, and const with respect to Hoisting and Scope. Use a table or bullet points for comparison.
Comparison of Variable Declarations:
-
var:- Scope: Function scoped. It ignores block scopes (like
iforfor). - Hoisting: Hoisted to the top of its scope and initialized with
undefined. Accessing it before declaration results inundefined. - Re-declaration: Can be re-declared and updated.
- Scope: Function scoped. It ignores block scopes (like
-
let:- Scope: Block scoped (valid only within
{}). - Hoisting: Hoisted but stays in the Temporal Dead Zone (TDZ) until the declaration line is reached. Accessing it before declaration throws a
ReferenceError. - Re-declaration: Cannot be re-declared in the same scope, but can be updated.
- Scope: Block scoped (valid only within
-
const:- Scope: Block scoped.
- Hoisting: Hoisted (in TDZ) like
let. - Re-declaration/Update: Cannot be re-declared or reassigned (immutable reference). Note: Object properties inside a
constcan still be mutated.
Explain the Prototype Chain and how Prototypal Inheritance works in JavaScript compared to Classical Inheritance.
Prototypal Inheritance is a feature where objects inherit properties and methods directly from other objects.
Prototype Chain:
Every JavaScript object has an internal property [[Prototype]] (accessible via __proto__) that points to another object. When a property is accessed:
- JS checks the object itself.
- If not found, it checks the object's prototype.
- It continues up the chain until it reaches
Object.prototype(whose prototype isnull).
Comparison with Classical Inheritance:
- Classical: Classes are blueprints. Objects are instances of classes. Inheritance defines a taxonomy of types ().
- Prototypal: Objects serve as prototypes for other objects. There are no classes (historically), just objects linking to objects. It is more dynamic and allows properties to be added at runtime.
Detailed the architecture of the JavaScript Event Loop. How does it handle concurrency despite JavaScript being single-threaded?
JavaScript is single-threaded, meaning it has one Call Stack. However, it handles concurrency using the Event Loop model, which involves the browser (or Node.js) runtime environment.
Components:
- Call Stack: Executes synchronous code (LIFO - Last In, First Out).
- Web APIs: Handles asynchronous operations (DOM events,
setTimeout,fetch). When an async task finishes, the callback is sent to a queue. - Callback Queue (Task Queue): Holds callbacks from things like
setTimeoutready for execution. - Microtask Queue: Holds callbacks from Promises and
MutationObserver. Has higher priority than the Callback Queue.
The Event Loop Mechanism:
The Event Loop constantly monitors the Call Stack and the Queues.
- If the Call Stack is empty, the Event Loop checks the Microtask Queue first and executes all tasks there.
- Once the Microtask Queue is empty, it picks the first task from the Callback Queue and pushes it onto the Call Stack.
This cycle allows non-blocking I/O operations.
Distinguish between Microtask and Macrotask queues. Provide an example code snippet where the execution order demonstrates the difference.
Both queues hold asynchronous callbacks, but they differ in priority.
- Macrotasks (Task Queue):
setTimeout,setInterval,setImmediate, I/O, UI rendering. - Microtasks:
Promise.then,catch,finally,queueMicrotask,MutationObserver.
Execution Rule:
After every macrotask execution (or initially after the script runs), the engine executes all microtasks in the queue before moving to the next macrotask.
Example:
javascript
console.log('Start'); // 1. Sync
setTimeout(() => {
console.log('Macrotask: Timeout'); // 4. Macrotask
}, 0);
Promise.resolve().then(() => {
console.log('Microtask: Promise'); // 3. Microtask
});
console.log('End'); // 2. Sync
Output: Start End Microtask: Promise Macrotask: Timeout.
Explain the concept of Callback Hell and how Promises resolve this issue.
Callback Hell (or the Pyramid of Doom) occurs when multiple asynchronous operations depend on each other, leading to deeply nested callbacks that are hard to read and debug.
Example of Callback Hell:
javascript
getData(function(a) {
getMoreData(a, function(b) {
getMoreData(b, function(c) {
console.log(c);
});
});
});
Solution: Promises
Promises provide a cleaner way to handle async chains using .then() methods. They represent a value that may be available now, in the future, or never.
Using Promises:
javascript
getData()
.then(a => getMoreData(a))
.then(b => getMoreData(b))
.then(c => console.log(c))
.catch(error => console.error(error));
This results in a linear, readable chain of operations with a centralized error handling mechanism.
What are the three states of a Promise? Explain the syntax and usage of Promise.all().
Three States of a Promise:
- Pending: The initial state, neither fulfilled nor rejected.
- Fulfilled (Resolved): The operation completed successfully.
- Rejected: The operation failed.
Promise.all(iterable):
This method takes an iterable of promises as an input and returns a single Promise.
- Resolves: When all of the input's promises fulfill. It returns an array of the results.
- Rejects: Immediately if any of the input promises reject (fail-fast behavior).
Usage:
javascript
const p1 = Promise.resolve(3);
const p2 = 42;
const p3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([p1, p2, p3]).then(values => {
console.log(values); // [3, 42, "foo"]
});
Discuss the Async/Await syntax in ES8. How does it improve asynchronous code readability compared to Promises?
Async/Await is syntactic sugar built on top of Promises. It allows asynchronous code to be written and look like synchronous code.
asynckeyword: Placed before a function declaration, ensuring the function returns a Promise.awaitkeyword: Can only be used insideasyncfunctions. It pauses the execution of the function until the Promise is resolved.
Improvement over Promises:
- Readability: It eliminates the need for
.then()chains, making the code look procedural. - Error Handling: It allows the use of standard
try...catchblocks for async errors, which is more intuitive than.catch(). - Debugging: Debugging
awaitsteps is often easier than stepping through promise callbacks in developer tools.
Explain Arrow Functions in ES6. specifically focusing on how they handle the this keyword differently than regular functions.
Arrow Functions provide a concise syntax for writing function expressions.
Syntax: const add = (a, b) => a + b;
Handling of this:
- Regular Functions: The value of
thisdepends on how the function is called (dynamic scoping). If called as a method,thisis the object; if called as a standalone function,thisis global (or undefined in strict mode). - Arrow Functions: They do not have their own
this. They inheritthisfrom the parent scope at the time they are defined (Lexical Scoping).
Implication: This makes Arrow Functions ideal for callbacks inside methods (like in setTimeout or event listeners) where you want to preserve the context of the outer method without using .bind(this).
Describe ES6 Destructuring for Arrays and Objects with examples. How does it simplify data extraction?
Destructuring is a syntax that allows unpacking values from arrays or properties from objects into distinct variables.
1. Object Destructuring:
Extracts properties by name.
javascript
const user = { name: 'Alice', age: 25 };
const { name, age } = user;
// name = 'Alice', age = 25
2. Array Destructuring:
Extracts values by position.
javascript
const colors = ['red', 'green', 'blue'];
const [first, second] = colors;
// first = 'red', second = 'green'
Simplification:
It reduces boilerplate code. Instead of accessing obj.prop repeatedly or using temporary variables like var x = arr[0], you can declare and assign multiple variables in a single line.
Explain the Spread Operator and Rest Parameter ($...$) in JavaScript. Provide use cases for both.
Both use the ... syntax but in opposite contexts.
1. Spread Operator (Expands):
Expands an iterable (like an array) into individual elements.
- Use Case: Merging arrays or copying objects.
javascript
const arr1 = [1, 2];
const arr2 = [...arr1, 3, 4]; // [1, 2, 3, 4]
2. Rest Parameter (Collects):
Collects multiple elements and condenses them into a single array element. It must be the last parameter in a function definition.
- Use Case: Handling an indefinite number of function arguments.
javascript
function sum(...numbers) {
return numbers.reduce((acc, curr) => acc + curr, 0);
}
sum(1, 2, 3); // 6
Compare ES Modules (import/export) with CommonJS modules. Why are ES Modules preferred in modern frontend development?
Comparison:
| Feature | CommonJS (Node.js legacy) | ES Modules (Modern Standard) |
|---|---|---|
| Syntax | require() / module.exports |
import / export |
| Loading | Synchronous (blocking) | Asynchronous |
| Usage | Dynamic (can call require conditionally) | Static structure (analyzed at compile time) |
| Scope | Copy of values | Live bindings (references) |
Why ES Modules are preferred:
- Tree Shaking: Because the structure is static, bundlers (like Webpack) can eliminate unused code during the build process.
- Browser Support: Modern browsers support ES modules natively (
<script type="module">). - Standardization: It is the official ECMAScript standard for JavaScript modules.
Define the Temporal Dead Zone (TDZ). Which variable declarations are affected by it and why?
The Temporal Dead Zone (TDZ) is the time span between the entering of a scope (block) and the actual declaration of a variable within that scope.
Affected Declarations:
letconstclass
Behavior:
Unlike var, which is hoisted and initialized to undefined, let and const are hoisted but uninitialized. Attempting to access them in the TDZ throws a ReferenceError.
Why?
It helps catch programming errors (accessing data before it exists) and enforces better coding practices. It also makes const work properly (it shouldn't change from undefined to a value).
Explain the concept of Function Currying using closures. Provide a mathematical example.
Currying is a functional programming technique where a function with multiple arguments is transformed into a sequence of functions, each taking a single argument.
mathematically:
Implementation using Closures:
Since inner functions retain access to outer scopes, currying relies on closures to remember previous arguments.
Example:
javascript
// Normal function
function multiply(a, b) {
return a * b;
}
// Curried function
function curriedMultiply(a) {
return function(b) {
return a * b;
}
}
const double = curriedMultiply(2); // Closure remembers a = 2
console.log(double(5)); // Output: 10
What are Template Literals in ES6? Explain Tagged Templates with an example.
Template Literals are string literals allowing embedded expressions, declared with backticks (`). They support multi-line strings and string interpolation ${variable}.
Tagged Templates:
A more advanced form where you parse a template literal with a function. The tag function receives the array of string parts and the interpolated values.
Example:
javascript
function highlight(strings, ...values) {
let str = '';
strings.forEach((string, i) => {
str += string + (values[i] ? <b>${values[i]}</b> : '');
});
return str;
}
const name = 'John';
const output = highlightHello ${name}, welcome!;
// Output: "Hello <b>John</b>, welcome!"
This is commonly used in libraries like Styled Components.
Derive the output of the following code snippet and explain the reasoning based on Event Loop and Scoping.
javascript
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000);
}
How would you fix it to print 0, 1, 2?
Output: 3, 3, 3
Reasoning:
varScope: The variableiis declared withvar, making it function-scoped (or global here). There is only one sharedivariable for the loop.- Asynchronous Execution: The
setTimeoutcallback is pushed to the Macrotask queue. The loop completes synchronously first. By the time the loop ends,ihas incremented to 3. - Execution: When the callbacks run after 1 second, they reference the shared
i, which is now 3.
Fix using let (Block Scope):
javascript
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000);
}
Reason: let creates a new lexical scope for each iteration of the loop, so each callback captures a different value of i (0, 1, 2).
Explain the usage of Classes in ES6. Are they different from the Prototypal model under the hood?
ES6 Classes provide a cleaner, more familiar syntax for creating objects and dealing with inheritance, similar to languages like Java or C#.
Syntax:
javascript
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(${this.name} speaks.);
}
}
Under the Hood:
Yes and No. Classes are primarily syntactic sugar over the existing Prototypal Inheritance model.
- The
classkeyword actually creates a function. - Methods defined in the class are added to the function's
prototypeproperty. extendshandles the prototype chain linking automatically.
However, there are minor differences: Classes are not hoisted, and code inside classes runs in strict mode automatically.
Compare Promise.all(), Promise.allSettled(), and Promise.race(). When would you use each?
1. Promise.all(iterable):
- Behavior: Waits for all to fulfill. Rejects immediately if one rejects.
- Use Case: When tasks are dependent on each other, and you need all data to proceed (e.g., fetching User and Permissions).
2. Promise.allSettled(iterable):
- Behavior: Waits for all to finish, regardless of whether they fulfilled or rejected. Returns an array of objects describing the outcome of each.
- Use Case: When you want to see the result of all operations and handle errors individually without stopping the flow (e.g., batch notification sending).
3. Promise.race(iterable):
- Behavior: Settles as soon as the first promise settles (resolves or rejects).
- Use Case: Timeouts. Race a fetch request against a 5-second timeout promise.