Common JavaScript Interview Questions: A Complete Guide for Developers
Aug 27, 2025 • 15 min read
JavaScript interviews can be nerve-wracking, but with the right preparation, you can ace them with confidence. Frontend development is an important part of web applications, and many companies are hiring skilled Frontend developers with expertise in HTML, CSS, JavaScript, and modern frameworks. Whether you’re a beginner or have years of experience, these questions cover the essential concepts that interviewers love to ask.
Let me walk you through the most common JavaScript interview questions, organized by difficulty level, with detailed explanations and practical examples.
Beginner Level Questions
1. What is JavaScript and where can it be used?
JavaScript is a high-level, interpreted, dynamic programming language primarily used to create interactive and dynamic content on websites. It runs in the browser (client-side), but it can also run on servers using environments like Node.js.
Key characteristics:
- Interpreted: No compilation step needed
- Dynamic: Variables can change types at runtime
- Multi-paradigm: Supports object-oriented, functional, and event-driven programming
// Client-side JavaScript
document.getElementById('myButton').addEventListener('click', function () {
alert('Hello, World!');
});
// Server-side JavaScript (Node.js)
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello from Node.js!');
});
2. What are the different data types in JavaScript?
JavaScript has primitive and non-primitive data types:
Primitive Types | Non-Primitive Types |
---|---|
undefined | Object |
null | Array |
boolean | Function |
number | Date |
string | RegExp |
symbol (ES6) | |
bigint (ES2020) |
// Primitive types
let name = 'John'; // string
let age = 25; // number
let isActive = true; // boolean
let data = null; // null
let user; // undefined
let id = Symbol('id'); // symbol
let bigNumber = 123n; // bigint
// Non-primitive types
let person = { name: 'John', age: 25 }; // object
let numbers = [1, 2, 3, 4, 5]; // array
let greet = function () {
console.log('Hi');
}; // function
// Type checking
console.log(typeof name); // "string"
console.log(typeof person); // "object"
console.log(typeof numbers); // "object" (arrays are objects!)
console.log(typeof greet); // "function"
3. What is the difference between var
, let
, and const
?
This is a fundamental question that tests your understanding of variable declarations:
// VAR - Function scoped, hoisted, can be redeclared
function varExample() {
if (true) {
var x = 1;
}
console.log(x); // 1 - accessible outside block
}
// Hoisting with var
console.log(hoistedVar); // undefined (not error)
var hoistedVar = 'I am hoisted';
// LET - Block scoped, hoisted but in temporal dead zone
function letExample() {
if (true) {
let y = 2;
}
// console.log(y); // ReferenceError: y is not defined
}
// CONST - Block scoped, must be initialized, cannot be reassigned
const PI = 3.14159;
// PI = 3.14; // TypeError: Assignment to constant variable
// However, objects and arrays can be mutated
const user = { name: 'John' };
user.name = 'Jane'; // This is allowed!
4. What is hoisting in JavaScript?
Hoisting is JavaScript’s default behavior of moving declarations to the top of their scope during the compilation phase:
// Function declarations are hoisted
sayHello(); // Works! Function is hoisted
function sayHello() {
console.log('Hello!');
}
// Variable declarations are hoisted, but not initializations
console.log(x); // undefined (not ReferenceError)
var x = 5;
// What actually happens:
var x; // Declaration is hoisted
console.log(x); // undefined
x = 5; // Assignment stays in place
// let and const are hoisted but not initialized (Temporal Dead Zone)
// console.log(y); // ReferenceError: Cannot access 'y' before initialization
let y = 10;
5. What are functions and how do you create them?
Functions are reusable blocks of code that can be called to perform specific tasks:
// Function Declaration (hoisted)
function greet(name) {
return `Hello, ${name}!`;
}
// Function Expression (not hoisted)
const greetExpression = function (name) {
return `Hello, ${name}!`;
};
// Arrow Function (ES6)
const greetArrow = (name) => `Hello, ${name}!`;
// Immediately Invoked Function Expression (IIFE)
(function () {
console.log('I run immediately!');
})();
// Function with default parameters
function createUser(name = 'Anonymous', age = 18) {
return { name, age };
}
console.log(greet('John')); // "Hello, John!"
console.log(greetExpression('Jane')); // "Hello, Jane!"
console.log(greetArrow('Bob')); // "Hello, Bob!"
console.log(createUser()); // { name: 'Anonymous', age: 18 }
Intermediate Level Questions
6. What are closures and how do they work?
Closures are one of the most important concepts in JavaScript. A closure is a function that has access to variables in its outer (enclosing) scope even after the outer function has returned:
function outerFunction(x) {
// This variable is accessible to the inner function
const outerVariable = x;
// Inner function (closure)
function innerFunction(y) {
return outerVariable + y; // Access to outerVariable
}
return innerFunction;
}
const addFive = outerFunction(5);
console.log(addFive(3)); // 8
// Practical example: Counter
function createCounter() {
let count = 0;
return {
increment: () => ++count,
decrement: () => --count,
getCount: () => count,
};
}
const counter = createCounter();
console.log(counter.getCount()); // 0
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.decrement()); // 1
7. What is the this
keyword and how does it work?
The this
keyword refers to the object that is currently executing the function. Its value depends on how the function is called:
// Global context
console.log(this); // Window (browser) or global (Node.js)
// Object method
const person = {
name: 'John',
greet: function () {
console.log(`Hello, I'm ${this.name}`);
},
};
person.greet(); // "Hello, I'm John"
// Constructor function
function Person(name) {
this.name = name;
this.greet = function () {
console.log(`Hello, I'm ${this.name}`);
};
}
const john = new Person('John');
john.greet(); // "Hello, I'm John"
// Event handler
document.getElementById('button').addEventListener('click', function () {
console.log(this); // The button element
});
// Arrow functions don't bind their own 'this'
const arrowPerson = {
name: 'Jane',
greet: () => {
console.log(`Hello, I'm ${this.name}`); // undefined
},
};
// Fix with regular function or bind
const boundPerson = {
name: 'Jane',
greet: function () {
console.log(`Hello, I'm ${this.name}`);
}.bind(this),
};
8. What are promises and how do you use them?
Promises are objects representing the eventual completion or failure of an asynchronous operation:
// Creating a promise
const myPromise = new Promise((resolve, reject) => {
// Simulate async operation
setTimeout(() => {
const random = Math.random();
if (random > 0.5) {
resolve(`Success! Random number: ${random}`);
} else {
reject(`Failed! Random number: ${random}`);
}
}, 1000);
});
// Using promises
myPromise
.then((result) => {
console.log('Success:', result);
})
.catch((error) => {
console.log('Error:', error);
})
.finally(() => {
console.log('Promise completed');
});
// Promise chaining
function fetchUser(id) {
return fetch(`/api/users/${id}`)
.then((response) => response.json())
.then((user) => {
console.log('User:', user);
return user;
})
.catch((error) => {
console.error('Error fetching user:', error);
});
}
// Promise.all - wait for all promises to resolve
const promises = [fetch('/api/users/1'), fetch('/api/users/2'), fetch('/api/users/3')];
Promise.all(promises)
.then((responses) => Promise.all(responses.map((r) => r.json())))
.then((users) => console.log('All users:', users))
.catch((error) => console.error('One of the requests failed:', error));
9. What is async/await and how does it work?
Async/await is syntactic sugar over promises that makes asynchronous code look and behave more like synchronous code:
// Converting promise-based code to async/await
async function fetchUserData(id) {
try {
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
return user;
} catch (error) {
console.error('Error:', error);
throw error;
}
}
// Using the async function
async function displayUser(id) {
try {
const user = await fetchUserData(id);
console.log('User:', user);
} catch (error) {
console.log('Failed to fetch user');
}
}
// Multiple async operations in parallel
async function fetchMultipleUsers(ids) {
const promises = ids.map((id) => fetchUserData(id));
const users = await Promise.all(promises);
return users;
}
// Async function always returns a promise
async function getData() {
return 'Hello World';
}
getData().then((result) => console.log(result)); // "Hello World"
10. What is the event loop and how does it work?
The event loop is what allows JavaScript to perform non-blocking operations despite being single-threaded:
console.log('1. Start');
setTimeout(() => {
console.log('2. Timeout callback');
}, 0);
Promise.resolve().then(() => {
console.log('3. Promise microtask');
});
console.log('4. End');
// Output order:
// 1. Start
// 4. End
// 3. Promise microtask
// 2. Timeout callback
// Event loop phases:
// 1. Call Stack (synchronous code)
// 2. Microtask Queue (Promises, queueMicrotask)
// 3. Macrotask Queue (setTimeout, setInterval, I/O)
// Example with different timing
console.log('Start');
setTimeout(() => console.log('Timeout 0'), 0);
setTimeout(() => console.log('Timeout 100'), 100);
Promise.resolve().then(() => console.log('Promise 1'));
Promise.resolve().then(() => console.log('Promise 2'));
console.log('End');
// Output:
// Start
// End
// Promise 1
// Promise 2
// Timeout 0
// Timeout 100
Advanced Level Questions
11. What are prototypes and how does inheritance work?
JavaScript uses prototypal inheritance where objects can inherit properties and methods from other objects:
// Constructor function
function Animal(name) {
this.name = name;
}
// Adding methods to prototype
Animal.prototype.speak = function () {
return `${this.name} makes a sound`;
};
// Creating instances
const dog = new Animal('Rex');
console.log(dog.speak()); // "Rex makes a sound"
// Inheritance
function Dog(name, breed) {
Animal.call(this, name); // Call parent constructor
this.breed = breed;
}
// Set up prototype chain
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
// Add Dog-specific methods
Dog.prototype.bark = function () {
return `${this.name} barks loudly!`;
};
const myDog = new Dog('Buddy', 'Golden Retriever');
console.log(myDog.speak()); // "Buddy makes a sound"
console.log(myDog.bark()); // "Buddy barks loudly!"
// Modern ES6 class syntax
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound`;
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
bark() {
return `${this.name} barks loudly!`;
}
}
12. What are modules and how do you use them?
Modules allow you to split your code into separate files and import/export functionality:
// math.js - Exporting
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
// Default export
export default class Calculator {
add(a, b) {
return a + b;
}
subtract(a, b) {
return a - b;
}
}
// utils.js - Named exports
export const formatDate = (date) => date.toLocaleDateString();
export const validateEmail = (email) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
// main.js - Importing
import { add, multiply, PI } from './math.js';
import Calculator from './math.js';
import { formatDate, validateEmail } from './utils.js';
console.log(add(5, 3)); // 8
console.log(multiply(4, 2)); // 8
console.log(PI); // 3.14159
const calc = new Calculator();
console.log(calc.subtract(10, 3)); // 7
// Dynamic imports
async function loadModule() {
const module = await import('./dynamic-module.js');
module.doSomething();
}
13. What is destructuring and how do you use it?
Destructuring allows you to extract values from objects and arrays into distinct variables:
// Array destructuring
const numbers = [1, 2, 3, 4, 5];
const [first, second, ...rest] = numbers;
console.log(first); // 1
console.log(second); // 2
console.log(rest); // [3, 4, 5]
// Object destructuring
const person = {
name: 'John',
age: 30,
city: 'New York',
country: 'USA',
};
const { name, age, ...otherInfo } = person;
console.log(name); // 'John'
console.log(age); // 30
console.log(otherInfo); // { city: 'New York', country: 'USA' }
// Renaming variables
const { name: fullName, age: userAge } = person;
console.log(fullName); // 'John'
console.log(userAge); // 30
// Default values
const { name: userName = 'Anonymous', email = 'no-email@example.com' } = person;
console.log(userName); // 'John' (from person object)
console.log(email); // 'no-email@example.com' (default value)
// Function parameters
function processUser({ name, age, email = 'default@example.com' }) {
console.log(`Processing ${name} (${age}) at ${email}`);
}
processUser({ name: 'Jane', age: 25 }); // "Processing Jane (25) at default@example.com"
14. What are the spread and rest operators?
The spread (...
) and rest operators allow you to work with arrays and objects more efficiently:
// Spread operator for arrays
const fruits = ['apple', 'banana'];
const moreFruits = [...fruits, 'orange', 'grape'];
console.log(moreFruits); // ['apple', 'banana', 'orange', 'grape']
// Copying arrays
const originalArray = [1, 2, 3];
const copyArray = [...originalArray];
copyArray.push(4);
console.log(originalArray); // [1, 2, 3]
console.log(copyArray); // [1, 2, 3, 4]
// Spread operator for objects
const person = { name: 'John', age: 30 };
const personWithEmail = { ...person, email: 'john@example.com' };
console.log(personWithEmail); // { name: 'John', age: 30, email: 'john@example.com' }
// Merging objects
const defaults = { theme: 'dark', language: 'en' };
const userPrefs = { theme: 'light' };
const finalPrefs = { ...defaults, ...userPrefs };
console.log(finalPrefs); // { theme: 'light', language: 'en' }
// Rest operator in function parameters
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15
// Rest operator in destructuring
const [first, second, ...remaining] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(second); // 2
console.log(remaining); // [3, 4, 5]
// Combining spread and rest
function processItems(first, second, ...rest) {
console.log('First:', first);
console.log('Second:', second);
console.log('Rest:', rest);
}
processItems('a', 'b', 'c', 'd', 'e');
// First: a
// Second: b
// Rest: ['c', 'd', 'e']
15. What are template literals and tagged templates?
Template literals are string literals that allow embedded expressions and multiline strings:
// Basic template literals
const name = 'John';
const age = 30;
const message = `Hello, my name is ${name} and I am ${age} years old.`;
console.log(message); // "Hello, my name is John and I am 30 years old."
// Multiline strings
const multiline = `
This is a
multiline string
that preserves formatting
`;
console.log(multiline);
// Expressions in template literals
const items = ['apple', 'banana', 'orange'];
const list = `
Shopping list:
${items.map((item) => `- ${item}`).join('\n')}
`;
console.log(list);
// Tagged templates
function highlight(strings, ...values) {
let result = '';
strings.forEach((string, i) => {
result += string;
if (values[i]) {
result += `<span class="highlight">${values[i]}</span>`;
}
});
return result;
}
const highlighted = highlight`Hello ${name}, you are ${age} years old!`;
console.log(highlighted); // "Hello <span class="highlight">John</span>, you are <span class="highlight">30</span> years old!"
// Practical example: SQL query builder
function sql(strings, ...values) {
let query = '';
strings.forEach((string, i) => {
query += string;
if (values[i] !== undefined) {
query += `'${values[i]}'`;
}
});
return query;
}
const tableName = 'users';
const userId = 123;
const query = sql`SELECT * FROM ${tableName} WHERE id = ${userId}`;
console.log(query); // "SELECT * FROM 'users' WHERE id = '123'"
Problem-Solving Questions
16. Implement a debounce function
Debouncing limits how often a function can be called:
function debounce(func, delay) {
let timeoutId;
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// Example usage
const expensiveOperation = debounce(() => {
console.log('Expensive operation executed');
}, 300);
// This will only execute once after 300ms of inactivity
expensiveOperation();
expensiveOperation();
expensiveOperation();
// Output: "Expensive operation executed" (after 300ms)
17. Implement a throttle function
Throttling ensures a function is called at most once in a specified time period:
function throttle(func, limit) {
let inThrottle;
return function (...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}
// Example usage
const throttledScroll = throttle(() => {
console.log('Scroll event handled');
}, 100);
// This will execute at most once every 100ms
window.addEventListener('scroll', throttledScroll);
18. Implement a deep clone function
Creating a deep copy of objects and arrays:
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (obj instanceof Date) {
return new Date(obj.getTime());
}
if (obj instanceof Array) {
return obj.map((item) => deepClone(item));
}
if (typeof obj === 'object') {
const cloned = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}
}
// Example usage
const original = {
name: 'John',
age: 30,
hobbies: ['reading', 'coding'],
address: {
city: 'New York',
country: 'USA',
},
birthDate: new Date('1990-01-01'),
};
const cloned = deepClone(original);
cloned.hobbies.push('gaming');
cloned.address.city = 'Los Angeles';
console.log(original.hobbies); // ['reading', 'coding']
console.log(cloned.hobbies); // ['reading', 'coding', 'gaming']
console.log(original.address.city); // 'New York'
console.log(cloned.address.city); // 'Los Angeles'
Interview Strategies and Best Practices
How to Approach Coding Problems
-
Understand the problem
- Ask clarifying questions
- Identify inputs, outputs, and constraints
- Consider edge cases
-
Plan your solution
- Think about different approaches
- Consider time and space complexity
- Choose the best algorithm
-
Implement step by step
- Write clean, readable code
- Use meaningful variable names
- Add comments for complex logic
-
Test your solution
- Run through examples
- Consider edge cases
- Verify time/space complexity
// Example: Two Sum problem
function twoSum(nums, target) {
// Approach 1: Brute force O(n²)
// for (let i = 0; i < nums.length; i++) {
// for (let j = i + 1; j < nums.length; j++) {
// if (nums[i] + nums[j] === target) {
// return [i, j];
// }
// }
// }
// Approach 2: Hash map O(n)
const numToIndex = new Map();
for (let i = 0; i < nums.length; i++) {
const complement = target - nums[i];
if (numToIndex.has(complement)) {
return [numToIndex.get(complement), i];
}
numToIndex.set(nums[i], i);
}
return []; // No solution found
}
// Test cases
console.log(twoSum([2, 7, 11, 15], 9)); // [0, 1]
console.log(twoSum([3, 2, 4], 6)); // [1, 2]
console.log(twoSum([3, 3], 6)); // [0, 1]
Key Interview Strategies
- Think Out Loud: Explain your thought process as you solve problems
- Start Simple: Begin with a brute force solution, then optimize
- Ask Questions: Clarify requirements and constraints
- Test Your Code: Walk through examples and edge cases
- Consider Performance: Discuss time and space complexity
- Practice Regularly: Solve problems on platforms like LeetCode, HackerRank
Most Frequently Asked Concepts
Based on industry surveys and my experience interviewing candidates:
Concept | Frequency | Difficulty | Key Points |
---|---|---|---|
Closures | Very High | Medium | Scope, data privacy, common pitfalls |
Promises/Async-Await | Very High | Medium | Error handling, chaining, Promise.all |
this binding | High | Medium | Context rules, arrow functions, bind/call/apply |
Event Loop | High | High | Call stack, callback queue, microtasks |
Prototypes | Medium | High | Inheritance, prototype chain, constructor functions |
Hoisting | High | Low | var vs let/const, function declarations |
Array Methods | Very High | Low | map, filter, reduce, forEach |
ES6 Features | High | Medium | Destructuring, spread/rest, template literals |
Final Preparation Checklist
- Core JavaScript: Variables, functions, objects, arrays
- Asynchronous JavaScript: Promises, async/await, callbacks
- DOM Manipulation: Event handling, traversal, modification
- ES6+ Features: Classes, modules, destructuring, arrow functions
- Problem-Solving: Algorithm questions, debugging scenarios
- Performance: Memory management, optimization techniques
- Testing: Unit tests, debugging strategies
- Practical Projects: Be ready to discuss your real-world experience
Remember, the key to succeeding in JavaScript interviews isn’t just memorizing syntax—it’s understanding the underlying concepts and being able to apply them to solve real problems. Practice regularly, build projects, and don’t be afraid to explain your reasoning during interviews.
Good luck with your JavaScript interviews! 🚀