How would you describe JavaScript and where does it run?
Answer
JavaScript is a lightweight, dynamically-typed language originally for browsers, now running on servers via Node.js, making it versatile for full-stack development.
These are the JavaScript questions you'll encounter in technical interviews, each with a detailed written answer. Master the fundamentals, then work through intermediate and advanced challenges.
JavaScript is a versatile, dynamically typed language that powers interactive web experiences. Originally created for browsers, it now runs on servers via Node.js and has become the backbone of modern web development.
JavaScript interviews test your understanding of core concepts like closures, the event loop, prototypes, and asynchronous programming. You'll be asked about promises, async/await, and how the JavaScript engine works under the hood.
Master these fundamentals and you'll be ready for frontend, backend, and full-stack roles. JavaScript proficiency is essential for any modern web developer.
Reading answers is not the same as giving them. Sit a full mock interview and get scored on how you actually explain them.
Try a mock interviewAnswer
JavaScript is a lightweight, dynamically-typed language originally for browsers, now running on servers via Node.js, making it versatile for full-stack development.
Answer
The DOM represents the HTML document as a tree. JavaScript accesses it via document object to read/modify elements, attributes, and content dynamically.
Answer
When a function is defined inside another, it captures the outer scope's variables. Those variables remain accessible even after the outer function completes.
Answer
Hoisting moves declarations to the top during compilation. var and function declarations are fully hoisted; let/const are hoisted but not initialized (temporal dead zone).
Answer
var has function scope. let/const have block scope (if, loop). const prevents reassignment but allows mutation of objects/arrays.
Answer
Deeply nested callbacks become hard to read. Promises chain with .then(); async/await makes asynchronous code look synchronous, improving readability.
Answer
The event loop checks if the call stack is empty, then processes microtasks (Promises) before macrotasks (setTimeout), enabling asynchronous I/O without threads.
Answer
Objects inherit from other objects via the prototype chain. ES6 classes are syntactic sugar over prototypes, making inheritance feel more familiar to classical programmers.
Answer
In methods, this is the calling object. In functions, it's the global object (or undefined in strict mode). Arrow functions inherit this from enclosing scope. .call, .apply, .bind can override it.
Answer
Destructuring unpacks values: const {name, age} = person extracts properties. Array destructuring: const [first, ...rest] = array. Makes code more readable.
Answer
JavaScript is dynamically-typed, meaning types are determined at runtime. Variables can hold any type and change types. It's interpreted, executing line-by-line in the browser or Node.js. JavaScript is single-threaded but handles asynchronous I/O through callbacks, Promises, and async/await, making it responsive without multi-threading. It's prototype-based, allowing flexible object creation and inheritance. Modern JavaScript (ES6+) adds classes, modules, arrow functions, and const/let for better code organization. Its flexibility enables rapid development but requires discipline to avoid type-related bugs.
Answer
The browser parses HTML into a tree where each element is a node. The document object provides access to this tree. JavaScript can query elements: document.getElementById, querySelector. It reads/writes properties: element.textContent, element.style.color. It modifies the tree: appendChild, removeChild, innerHTML. Event listeners attach to elements: element.addEventListener('click', handler). Changes trigger reflows (recalculating layout) and repaints (redrawing). DOM manipulation is reactive: changing properties immediately affects the rendered page. Performance-conscious code batches DOM changes to minimize reflows.
Answer
When a function is created, it captures variables from its enclosing scope. Even after the outer function returns, the inner function retains access through the closure. Example: function outer() { const x = 5; return function inner() { return x; } } The returned function remembers x. This enables data privacy: x is inaccessible except through inner(). Closures power callbacks and event handlers. The captured variables persist in memory as long as the closure exists, potentially causing memory leaks if not cleaned up.
Answer
During compilation, declarations are moved to the top of their scope. var declarations are hoisted and initialized as undefined, so accessing before the line where it's declared returns undefined (not an error). Function declarations are fully hoisted; they're callable before the declaration. let/const are hoisted but not initialized, creating a temporal dead zone: accessing before declaration throws ReferenceError. Arrow functions are treated like variables (hoisted if const, temporal dead zone). Classes are hoisted but behave like let/const regarding the temporal dead zone. Understanding hoisting prevents unexpected undefined values.
Answer
var is function-scoped: var x = 1; inside an if block is accessible outside the block. let/const are block-scoped: let x = 1; inside {} is inaccessible outside. var can be redeclared and reassigned. let can be reassigned but not redeclared in the same scope. const cannot be reassigned or redeclared. const prevents reassignment but allows mutation: const obj = {}; obj.prop = 1 is valid. Modern code prefers const (immutability) and let (clear scope), avoiding var.
Answer
Callbacks passed to functions execute when the operation completes. Nested callbacks (callback hell) become hard to read and error-prone. Promises represent eventual completion/failure, returning a Promise object allowing chaining: promise.then(success).catch(error). async/await builds on Promises, making code look synchronous: const result = await operation(); Code flows top-to-bottom, easier to follow. Error handling uses try-catch, not nested callbacks. async/await handles sequences naturally: result1 = await op1(); result2 = await op2(result1). Readability improves, reducing bugs.
Answer
The call stack executes synchronous code. Asynchronous operations (setTimeout, fetch, event handlers) add tasks to the task queue (macrotasks); Promises add to the microtask queue. The event loop runs: (1) Execute all call stack code, (2) Execute all microtasks, (3) Execute one macrotask, (4) Repeat. Microtasks run before the next macrotask, so Promises resolve before setTimeout callbacks. This single-threaded model with queuing enables handling I/O without threads. Understanding the order prevents timing bugs and performance issues.
Answer
Every object has a prototype, accessed via __proto__ or Object.getPrototypeOf(). When a property is not found on the object, JavaScript searches up the prototype chain. Setting obj.__proto__ = baseObj chains them. Function constructors: function Base() {}; function Child() {}; Child.prototype = Object.create(Base.prototype); sets up inheritance. ES6 classes: class Child extends Base {} is syntactic sugar over prototypes. The new operator creates an object with the constructor's prototype. Understanding the chain enables writing reusable hierarchies.
Answer
In methods, this is the calling object: obj.method() has this as obj. In functions, this is the global object (or undefined in strict mode). Arrow functions inherit this from the enclosing scope, ignoring the call context. .call(obj, args) executes a function with this as obj. .apply(obj, [args]) is like .call but takes an array. .bind(obj) returns a new function with this bound to obj. .bind is useful for event handlers and callbacks where context is lost. Understanding binding prevents the common bug of losing this in callbacks.
Answer
Object destructuring: const {x, y} = obj extracts x and y properties. Nested: const {outer: {inner}} = obj. Renamed: const {x: newX} = obj. Defaults: const {x = 0} = obj. Array destructuring: const [a, b, c] = arr. Skip: const [a, , c] = arr skips b. Rest: const [first, ...rest] = arr. Function parameters: function({x, y}) {} destructures the argument. Destructuring reduces boilerplate: accessing properties happens automatically. It's especially useful in function parameters and when extracting specific values from complex objects, improving code readability.
This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.
Answer
I used async/await with try-catch: try { const response = await fetch(url); const data = await response.json(); } catch (error) { console.error(error); }. I checked response.ok before parsing. For multiple requests, I used Promise.all: const [data1, data2] = await Promise.all([fetch1(), fetch2()]); Concurrent requests were faster than sequential.
This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.
Answer
A callback lost this context when passed as an event handler. Inside the callback, this was undefined instead of the object. I fixed it by using arrow functions: element.addEventListener('click', () => this.method()); or by binding: element.addEventListener('click', this.method.bind(this)); Arrow functions inherit this from the enclosing scope.
This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.
Answer
I created a module exporting functions that shared private state: const counter = (() => { let count = 0; return { increment: () => ++count, get: () => count }; })(); count is inaccessible outside; only increment() and get() expose controlled access. This pattern protects internal state from external modification.
This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.
Answer
Legacy code had deeply nested callbacks handling sequential API calls. Refactoring to async/await flattened the structure: const user = await fetchUser(); const posts = await fetchPosts(user.id); Code was immediately easier to follow. Error handling with try-catch replaced nested .catch() handlers.
This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.
Answer
I queried parent elements, attaching event listeners there instead of on individual items: parent.addEventListener('click', (e) => { if (e.target.matches('.item')) { handleItemClick(e); } }); Event delegation handles dynamically created elements without reattaching listeners for each new item.
This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.
Answer
Instead of accessing object properties repeatedly in function parameters, I destructured: function displayUser({ name, email, avatar }) { instead of function displayUser(user) { const name = user.name; const email = user.email; ... }. Defaults reduced null checks: { name = 'Guest' } = {}.
This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.
Answer
I implemented memoization for expensive computations: const memoize = (fn) => { const cache = {}; return (arg) => { if (!(arg in cache)) cache[arg] = fn(arg); return cache[arg]; }; }. Calling memoize(expensiveFunction) returned a cached version. Repeated calls with the same argument returned cached results instantly.
This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.
Answer
A closure captured a large dataset in a loop, preventing garbage collection. Each closure retained the entire dataset. I fixed it by limiting captured variables or using weak references. I ensured event listeners and closures were cleaned up when no longer needed: element.removeEventListener or assigning null to references.
This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.
Answer
I organized related functions in a module: const userModule = (() => { const users = []; return { add: (u) => users.push(u), getAll: () => [...users] }; })(); The module had private state (users) and public API (add, getAll). This pattern is cleaner than global functions.
This browser cannot record audio. Answer the question out loud anyway, then compare yourself against the model answer below.
Answer
I created a Shape base: function Shape(x, y) { this.x = x; this.y = y; }. Circle inherited: function Circle(x, y, r) { Shape.call(this, x, y); this.r = r; }; Circle.prototype = Object.create(Shape.prototype); Circle.prototype.constructor = Circle;. ES6 classes were simpler: class Circle extends Shape { constructor(x, y, r) { super(x, y); this.r = r; } }.
// Fetch and Display Data
// Implement the solution hereAnswer
async function fetchAndDisplay(url) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error('Network response was not ok');
const data = await response.json();
const container = document.getElementById('data-container');
container.innerHTML = JSON.stringify(data, null, 2);
} catch (error) {
console.error('Error:', error);
}
}// Event Delegation Handler
// Implement the solution hereAnswer
const list = document.getElementById('list');
list.addEventListener('click', (event) => {
if (event.target.tagName === 'LI') {
console.log('Clicked item:', event.target.textContent);
event.target.classList.toggle('selected');
}
});// Closure Counter
// Implement the solution hereAnswer
function createCounter() {
let count = 0;
return {
increment: function() { return ++count; },
decrement: function() { return --count; },
getCount: function() { return count; }
};
}
const counter = createCounter();// Promise Chain
// Implement the solution hereAnswer
fetch('/user')
.then(response => response.json())
.then(user => fetch(`/posts/${user.id}`))
.then(response => response.json())
.then(posts => console.log(posts))
.catch(error => console.error(error));// DOM Query and Manipulation
// Implement the solution hereAnswer
const heading = document.querySelector('h1');
heading.textContent = 'New Title';
heading.style.color = 'blue';
heading.setAttribute('data-id', '123');
const items = document.querySelectorAll('.item');
items.forEach(item => item.classList.add('active'));// Array Methods with Callbacks
// Implement the solution hereAnswer
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(x => x * 2);
const evens = numbers.filter(x => x % 2 === 0);
const sum = numbers.reduce((acc, x) => acc + x, 0);// Debounce Function
// Implement the solution hereAnswer
function debounce(fn, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn(...args), delay);
};
}
const debouncedSearch = debounce((query) => fetch(`/search?q=${query}`), 300);// Object Destructuring
// Implement the solution hereAnswer
const user = { name: 'Alice', age: 30, email: 'alice@example.com' };
const { name, age, email = 'unknown' } = user;
const [first, ...rest] = [1, 2, 3, 4];
function greet({ name, age }) { console.log(`Hello ${name}, age ${age}`); }// Module Pattern
// Implement the solution hereAnswer
const userModule = (() => {
const users = [];
return {
addUser: (user) => { users.push(user); },
getUsers: () => [...users],
getUserById: (id) => users.find(u => u.id === id)
};
})();
userModule.addUser({ id: 1, name: 'Alice' });// Prototype Inheritance
// Implement the solution hereAnswer
function Animal(name) { this.name = name; }
Animal.prototype.speak = function() { console.log(this.name + ' speaks'); };
function Dog(name, breed) { Animal.call(this, name); this.breed = breed; }
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
const dog = new Dog('Rex', 'Labrador');