interview question
Q1: What is event delegation in JavaScript, and can you provide a practical example of when and how you would use it? Please include a code example in your explanation.
Event delegation is a technique in JavaScript where an event listener is attached to a parent element instead of individual child elements. This allows the event to "flow" through the DOM, either upward (bubbling phase) or downward (capturing phase), depending on how the listener is configured.
There are two types of event propagation:
- Event Bubbling: The event starts from the target element (the one that triggered the event) and bubbles up through its ancestors in the DOM hierarchy.
- Event Capturing: The event starts from the root of the DOM and flows down to the target element.
// Instead of this (inefficient)
const buttons = document.querySelectorAll('.product-button');
buttons.forEach(button => {
button.addEventListener('click', function(e) {
// handle click
});
});
// Better approach with event delegation
document.querySelector('.products-container').addEventListener('click', function(e) {
if (e.target.matches('.product-button')) {
const productId = e.target.dataset.productId;
// handle the product click
}
});
Q2: Explain the difference between let, const, and var. Additionally, what is temporal dead zone (TDZ) in JavaScript and how does it relate to these declarations?
In JavaScript, let, const, and var are used to declare variables. let and const were introduced in ES6, while earlier, only var was available.
let and const are block-scoped, meaning they are accessible only within the block where they are defined, while var is function-scoped. Variables declared with let and var can be reassigned, but const cannot be reassigned after its initial assignment. All three (let, const, and var) are hoisted. However, let and const are not initialized during hoisting, which is why using them before their declaration results in an error. On the other hand, var is initialized to undefined, so it does not throw an error but returns undefined.
// Var hoisting
console.log(varVariable); // undefined
var varVariable = 1;
// Let/Const hoisting and TDZ
console.log(letVariable); // ReferenceError: Cannot access before initialization
let letVariable = 2;
// Const with objects
const obj = { name: 'John' };
obj.name = 'Jane'; // This works
obj = {}; // This throws error
// Block scope example
if (true) {
let blockScoped = 'only here';
var functionScoped = 'available outside';
}
console.log(functionScoped); // works
console.log(blockScoped); // ReferenceError
Q3: Explain closures in JavaScript. How would you use them to create private variables? Please provide a practical example where closures solve a real problem.
- Closures are formed with functions in JavaScript
- Functions have access to their parent scope/context
- This context remains accessible even after the function has finished executing
- Can be used to create private variables
// More practical real-world example
function createUserManager() {
// Private data
const users = new Map();
return {
addUser: function(id, name) {
users.set(id, { name, createdAt: Date.now() });
},
getUser: function(id) {
return users.get(id);
},
removeUser: function(id) {
users.delete(id);
},
// users Map is not directly accessible outside
};
}
// Usage
const userManager = createUserManager();
userManager.addUser(1, "John");
console.log(userManager.getUser(1)); // {name: "John", createdAt: ...}
console.log(userManager.users); // undefined - private!
// Another practical example - memoization
function createMemoizedFunction() {
const cache = new Map();
return function(n) {
if (cache.has(n)) {
console.log('Fetching from cache');
return cache.get(n);
}
console.log('Calculating...');
const result = /* expensive calculation */;
cache.set(n, result);
return result;
}
}
Q4: What is the difference between Promise.all(), Promise.race(), Promise.allSettled(), and Promise.any()? Provide examples of when you would use each one.
Key differences
- Promise.all(): Fails fast, returns all successes
- Promise.race(): Returns first settled (success or failure)
- Promise.any(): Returns first success, fails if all fail
- Promise.allSettled(): Returns all results, never rejects
// Promise.all()
// Returns array of all results, fails if ANY promise fails
// Use case: When you need ALL operations to succeed
Promise.all([
fetch('/user'),
fetch('/profile'),
fetch('/settings')
])
.then(([user, profile, settings]) => {
// All succeeded
})
.catch(error => {
// Any one failed
});
// Promise.race()
// Returns first promise to settle (resolve OR reject)
// Use case: Timeout operations
Promise.race([
fetch('/data'),
new Promise((_, reject) => setTimeout(() => reject('timeout'), 5000))
]);
// Promise.any()
// Returns first promise to SUCCESSFULLY resolve
// Use case: Try multiple APIs, need first success
Promise.any([
fetch('api1/data'),
fetch('api2/data'),
fetch('api3/data')
])
.then(firstSuccess => {})
.catch(error => {
// ALL promises failed
});
// Promise.allSettled()
// Returns array of ALL results whether success or failure
// Use case: When you need to know ALL outcomes
Promise.allSettled([
fetch('/api1'),
fetch('/api2'),
fetch('/api3')
])
.then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('Success:', result.value);
} else {
console.log('Failed:', result.reason);
}
});
});