Introduction
JavaScript remains the backbone of web development, and mastering it is crucial for any developer looking to succeed in technical interviews. In this comprehensive guide, we'll explore the top 10 JavaScript interview questions that consistently appear across companies of all sizes, from startups to FAANG.
1. Explain the Difference Between var, let, and const
This is often the opening question in JavaScript interviews, and for good reason. Understanding variable declarations reveals your grasp of scope, hoisting, and modern JavaScript practices.
var was the original way to declare variables in JavaScript. Variables declared with var are function-scoped, meaning they're accessible anywhere within the function where they're declared. They're also hoisted to the top of their scope, which can lead to unexpected behavior.
let was introduced in ES6 and provides block-scoping. This means a variable declared with let is only accessible within the block(denoted by curly braces) where it's defined. Unlike var, let variables are not initialized until their declaration is evaluated, creating a "temporal dead zone."
const also provides block-scoping like let, but with an additional constraint: the variable cannot be reassigned after initialization. However, it's important to note that for objects and arrays, the contents can still be modified – only the reference itself is immutable.
// var example-function scoped
function varExample() {
if (true) {
var x = 10;
}
console.log(x); // 10 - accessible outside the if block
}
// let example-block scoped
function letExample() {
if (true) {
let y = 20;
}
console.log(y); // ReferenceError: y is not defined
}
// const example
const person = { name: "John" };
person.name = "Jane"; // Allowed-modifying property
person = {}; // TypeError-cannot reassign
2. What is Closure and Why Is It Important?
Closures are one of the most powerful features in JavaScript and a favorite topic among interviewers. A closure is created when a function "remembers" the variables from its outer scope even after the outer function has finished executing.
function createCounter() {
let count = 0; // This variable is "enclosed"
return {
increment: function() {
count++;
return count;
},
decrement: function() {
count--;
return count;
},
getCount: function() {
return count;
}
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount()); // 2
Closures enable data privacy, function factories, and are fundamental to many JavaScript patterns including the module pattern. They're also essential for working with callbacks, event handlers, and asynchronous code.
3. Explain the Event Loop and Asynchronous JavaScript
Understanding the event loop is crucial for writing efficient JavaScript code. JavaScript is single-threaded, meaning it can only execute one piece of code at a time. The event loop is what allows JavaScript to perform non-blocking operations.
The event loop continuously checks the call stack and the callback queue. When the call stack is empty, it takes the first callback from the queue and pushes it onto the stack to be executed.
console.log('1'); // Synchronous-goes to call stack immediately
setTimeout(() => {
console.log('2'); // Asynchronous-goes to callback queue
}, 0);
Promise.resolve().then(() => {
console.log('3'); // Microtask-higher priority than callback queue
});
console.log('4'); // Synchronous
// Output: 1, 4, 3, 2
The microtask queue (for Promises) has higher priority than the macrotask queue (for setTimeout, setInterval). This is why '3' prints before '2' even though both are asynchronous.
4. What is Prototypal Inheritance?
Unlike classical inheritance found in languages like Java, JavaScript uses prototypal inheritance. Every object in JavaScript has a prototype, and objects can inherit properties and methods from their prototype.
// Constructor function
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(this.name + ' makes a sound.');
};
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
// Set up inheritance
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log(this.name + ' barks!');
};
const rex = new Dog('Rex', 'German Shepherd');
rex.speak(); // Rex makes a sound.
rex.bark(); // Rex barks!
Modern JavaScript also supports class syntax, which provides a cleaner way to work with prototypes, though it's still prototypal inheritance under the hood.
5. Explain 'this' Keyword in Different Contexts
The 'this' keyword is notoriously tricky in JavaScript because its value depends on how a function is called, not where it's defined.
Global context: In the global scope, 'this' refers to the global object (window in browsers, global in Node.js).
Object method: When a function is called as a method of an object, 'this' refers to that object.
Constructor: In a constructor function, 'this' refers to the newly created instance.
Arrow functions: Arrow functions don't have their own 'this' binding; they inherit 'this' from the enclosing scope.
Explicit binding: Using call(), apply(), or bind() allows you to explicitly set 'this'.
const obj = {
name: 'Object',
regularFunction: function() {
console.log(this.name); // 'Object'
},
arrowFunction: () => {
console.log(this.name); // undefined (inherits from global)
}
};
// Explicit binding
function greet() {
console.log('Hello, ' + this.name);
}
const person = { name: 'Alice' };
greet.call(person); // Hello, Alice
6. What are Promises and How Do They Work?
Promises represent the eventual completion or failure of an asynchronous operation. They provide a cleaner alternative to callback-based patterns and help avoid "callback hell."
A Promise can be in one of three states: pending, fulfilled, or rejected. Once settled (fulfilled or rejected), a Promise cannot change state.
function fetchUserData(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (userId > 0) {
resolve({ id: userId, name: 'John Doe' });
} else {
reject(new Error('Invalid user ID'));
}
}, 1000);
});
}
// Using the Promise
fetchUserData(1)
.then(user => {
console.log('User:', user);
return fetchUserPosts(user.id);
})
.then(posts => {
console.log('Posts:', posts);
})
.catch(error => {
console.error('Error:', error.message);
})
.finally(() => {
console.log('Operation complete');
});
// Async/await syntax (syntactic sugar for Promises)
async function getUserWithPosts(userId) {
try {
const user = await fetchUserData(userId);
const posts = await fetchUserPosts(user.id);
return { user, posts };
} catch (error) {
console.error('Error:', error.message);
throw error;
}
}
7. Explain Array Methods: map, filter, reduce
These higher-order functions are essential for functional programming in JavaScript and appear frequently in interviews.
map() transforms each element in an array and returns a new array of the same length.
filter() creates a new array with elements that pass a test.
reduce() reduces an array to a single value by applying a function to each element.
const numbers = [1, 2, 3, 4, 5];
// map-double each number
const doubled = numbers.map(n => n * 2);
// [2, 4, 6, 8, 10]
// filter-keep only even numbers
const evens = numbers.filter(n => n % 2 === 0);
// [2, 4]
// reduce-sum all numbers
const sum = numbers.reduce((acc, curr) => acc + curr, 0);
// 15
// Chaining methods
const result = numbers
.filter(n => n > 2)
.map(n => n * 2)
.reduce((acc, curr) => acc + curr, 0);
// 24 (3*2 + 4*2 + 5*2)
8. What is the Difference Between == and ===?
This question tests your understanding of type coercion in JavaScript.
== (loose equality) compares values after performing type coercion. JavaScript will attempt to convert the operands to the same type before comparison.
=== (strict equality) compares both value and type without coercion.
console.log(5 == '5'); // true (string converted to number)
console.log(5 === '5'); // false (different types)
console.log(null == undefined); // true
console.log(null === undefined); // false
console.log(0 == false); // true
console.log(0 === false); // false
// Always prefer === for predictable comparisons
9. Explain Hoisting in JavaScript
Hoisting is JavaScript's behavior of moving declarations to the top of their scope during compilation. However, only the declarations are hoisted, not the initializations.
console.log(x); // undefined (declaration hoisted, not initialization)
var x = 5;
console.log(y); // ReferenceError (let is not initialized until declaration)
let y = 10;
// Function declarations are fully hoisted
sayHello(); // Works!
function sayHello() {
console.log('Hello!');
}
// Function expressions are not fully hoisted
sayGoodbye(); // TypeError: sayGoodbye is not a function
var sayGoodbye = function() {
console.log('Goodbye!');
};
10. What are Higher-Order Functions?
Higher-order functions are functions that either take functions as arguments or return functions. They're fundamental to functional programming in JavaScript.
// Function that takes a function as an argument
function executeOperation(a, b, operation) {
return operation(a, b);
}
const add = (x, y) => x + y;
const multiply = (x, y) => x * y;
console.log(executeOperation(5, 3, add)); // 8
console.log(executeOperation(5, 3, multiply)); // 15
// Function that returns a function
function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
Conclusion
Mastering these JavaScript concepts will give you a solid foundation for technical interviews. Remember that interviewers aren't just looking for correct answers – they want to see your thought process, your ability to explain complex concepts clearly, and your practical understanding of when and why to use these features.
Practice implementing these concepts from scratch, and be prepared to discuss real-world scenarios where you've applied them. Good luck with your interviews!
