50 Must-know JavaScript Interview Questions by Ex-interviewers

50 essential JavaScript coding interview questions and answers, curated by senior engineers and former interviewers from leading tech companies.
Author
GreatFrontEnd Team
35 min read
Jan 27, 2025
50 Must-know JavaScript Interview Questions by Ex-interviewers

JavaScript is an essential skill for anyone pursuing a career in web development, but securing a job in this field can be particularly challenging for newcomers. A critical part of the hiring process is the technical interview, where your JavaScript expertise will be thoroughly evaluated. To support your preparation and build your confidence, we’ve put together a list of the top 50 must-know JavaScript interview questions and answers frequently encountered in interviews.

1. What is Debouncing in JavaScript?

Debouncing is a smart way to handle events that fire repeatedly within a short time, such as typing in a search box or resizing a window. Instead of executing a function every single time the event is triggered, debouncing ensures the function runs only after the event stops firing for a specified time.

Why is it important?

It prevents performance bottlenecks by reducing the number of unnecessary function calls, making your app smoother and more efficient.

How does it work?

The debounce method delays a function’s execution until after a defined "waiting period" has passed since the last event. Let’s see an example using Lodash:

import { debounce } from 'lodash';
const searchInput = document.getElementById('search-input');
const debouncedSearch = debounce(() => {
// Perform the search operation here
console.log('Searching for:', searchInput.value);
}, 300);
searchInput.addEventListener('input', debouncedSearch);

Key Features of Debouncing

  • Delay-based execution: Runs the function after user activity has stopped.
  • Improves performance: Prevents excessive computations or network calls during rapid events.
  • Flexible configurations: Supports leading (immediate) and trailing (delayed) execution, and even a maximum wait time.

How is it different from Throttling?

While debouncing waits until user activity stops, throttling ensures the function runs at fixed intervals, regardless of how often the event occurs. Each technique suits specific use cases, such as search boxes (debouncing) versus scroll events (throttling).

Explore and practice more Debouncing techniques on GreatFrontEnd

2. Understanding Promise.all

Promise.all() is a powerful method in JavaScript that allows you to handle multiple asynchronous tasks simultaneously. It takes an array of promises and returns a single promise that resolves when all the promises resolve, or rejects if any one of them fails.

Why is it useful?

This method is perfect when you need to wait for several independent asynchronous tasks to finish before proceeding, like fetching data from multiple APIs.

Example Usage

Here’s how Promise.all() works with multiple API requests:

const promise1 = fetch('https://api.example.com/data/1');
const promise2 = fetch('https://api.example.com/data/2');
const promise3 = fetch('https://api.example.com/data/3');
Promise.all([promise1, promise2, promise3])
.then((responses) => {
// Executes only when all promises are resolved.
console.log('All responses:', responses);
})
.catch((error) => {
// Catches any error from any promise.
console.error('Error:', error);
});

Key Features of Promise.all

  • Concurrency: Runs multiple asynchronous tasks in parallel, improving performance.
  • All-or-nothing resolution: The promise resolves only when all tasks succeed, or it rejects if any one fails.
  • Simplifies workflows: Ideal for managing interdependent or independent tasks efficiently.

Explore more and practice Promise.all on GreatFrontEnd

3. What is Deep Equal?

Deep equality involves comparing two objects or arrays to determine if they are structurally identical. Unlike shallow equality, which only checks if object references are the same, deep equality examines whether all nested values are equal.

Example Implementation

Here's a simple deepEqual function:

function deepEqual(obj1, obj2) {
if (obj1 === obj2) return true;
if (
obj1 == null ||
typeof obj1 !== 'object' ||
obj2 == null ||
typeof obj2 !== 'object'
)
return false;
let keys1 = Object.keys(obj1);
let keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
for (let key of keys1) {
if (!keys2.includes(key) || !deepEqual(obj1[key], obj2[key])) return false;
}
return true;
}
// Example usage
const object1 = {
name: 'John',
age: 30,
address: {
city: 'New York',
zip: '10001',
},
};
const object2 = {
name: 'John',
age: 30,
address: {
city: 'New York',
zip: '10001',
},
};
console.log(deepEqual(object1, object2)); // true

This function uses recursion to check nested properties, ensuring all values match in both objects or arrays. It's a critical concept for comparing complex data structures in frontend development.

Practice Deep Equal on GreatFrontEnd

4. Understanding Event Emitter

An EventEmitter is a utility that enables objects to listen for and emit events. It implements the observer pattern, allowing you to subscribe to actions or changes and handle them when triggered. This concept is fundamental in both JavaScript and Node.js for managing event-driven programming.

Example Usage

const eventEmitter = new EventEmitter();
// Subscribe to an event
eventEmitter.on('customEvent', (data) => {
console.log('Event emitted with data:', data);
});
// Emit the event
eventEmitter.emit('customEvent', { message: 'Hello, world!' });

EventEmitter allows flexible communication between components, making it useful in scenarios like state management, logging, or real-time updates.

Practice Event Emitter on GreatFrontEnd

5. What is Array.prototype.reduce()?

Array.prototype.reduce() is a versatile method for iterating through an array and reducing it to a single value. It processes each element with a callback function, carrying over an accumulator to build the final result. Common use cases include summing numbers, flattening arrays, or even building complex objects.

Example: Summing Array Elements

const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce(function (accumulator, currentValue) {
return accumulator + currentValue;
}, 0);
console.log(sum); // Output: 15

Why Use reduce?

  • Flexibility: Handles various operations, from aggregations to transformations.
  • Functional programming: Encourages declarative and clean code.
  • Powerful: Can replace loops or multiple utility methods in a single chain.

Practice building Array.protoype.reduce on GreatFrontEnd

6. Simplifying Arrays: Flattening

Flattening transforms a nested array into a single-level array, making it more manageable. Since ES2019, JavaScript provides the Array.prototype.flat() method for this.

// Example: Flattening a nested array
const nestedArray = [1, [2, [3, [4, [5]]]]];
const flatArray = nestedArray.flat(Infinity);
console.log(flatArray); // Output: [1, 2, 3, 4, 5]

Here, .flat(Infinity) ensures the entire array is flattened, no matter how deep. For less deeply nested arrays, you can specify the depth.

Before ES2019, custom solutions were common:

// Custom recursive array flattener
function flattenArray(arr) {
return arr.reduce(
(acc, val) =>
Array.isArray(val) ? acc.concat(flattenArray(val)) : acc.concat(val),
[],
);
}
const nestedArray = [1, [2, [3, [4, [5]]]]];
const flatArray = flattenArray(nestedArray);
console.log(flatArray); // Output: [1, 2, 3, 4, 5]

Practice Flattening Arrays on GreatFrontEnd

7. Merging Data Structures

Merging data is crucial when handling complex structures. JavaScript provides efficient ways to combine objects or arrays.

Merging Objects

Using the Spread Operator

The spread operator is concise and intuitive for merging objects:

const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const mergedObj = { ...obj1, ...obj2 };
console.log(mergedObj); // Output: { a: 1, b: 3, c: 4 }

Using Object.assign()

Another approach is Object.assign():

const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const mergedObj = Object.assign({}, obj1, obj2);
console.log(mergedObj); // Output: { a: 1, b: 3, c: 4 }

Merging Arrays

Using the Spread Operator

const array1 = [1, 2, 3];
const array2 = [4, 5, 6];
const mergedArray = [...array1, ...array2];
console.log(mergedArray); // Output: [1, 2, 3, 4, 5, 6]

Using Array.concat()

const array1 = [1, 2, 3];
const array2 = [4, 5, 6];
const mergedArray = array1.concat(array2);
console.log(mergedArray); // Output: [1, 2, 3, 4, 5, 6]

Deep Merging

For nested objects, you’ll need custom logic or libraries:

function deepMerge(target, source) {
for (const key in source) {
if (source[key] instanceof Object && key in target) {
Object.assign(source[key], deepMerge(target[key], source[key]));
}
}
Object.assign(target || {}, source);
return target;
}
const obj1 = { a: 1, b: { x: 10, y: 20 } };
const obj2 = { b: { y: 30, z: 40 }, c: 3 };
const mergedObj = deepMerge(obj1, obj2);
console.log(mergedObj); // Output: { a: 1, b: { x: 10, y: 30, z: 40 }, c: 3 }

Alternatively, libraries like Lodash simplify deep merging:

const _ = require('lodash');
const obj1 = { a: 1, b: { x: 10, y: 20 } };
const obj2 = { b: { y: 30, z: 40 }, c: 3 };
const mergedObj = _.merge({}, obj1, obj2);
console.log(mergedObj); // Output: { a: 1, b: { x: 10, y: 30, z: 40 }, c: 3 }

Practice Merging Data on GreatFrontEnd

8. Selecting DOM Elements: getElementsByClassName

getElementsByClassName fetches elements matching a specific class and returns them as a live HTMLCollection.

// Fetch and loop through elements
const elements = document.getElementsByClassName('example');
for (let i = 0; i < elements.length; i++) {
console.log(elements[i].textContent);
}

Multiple Classes

You can combine class names for more specific selections:

const elements = document.getElementsByClassName('class1 class2');

Live Collections

HTMLCollection updates automatically if DOM elements are added or removed.

For more complex selectors, use querySelectorAll:

const elements = document.querySelectorAll('.example');

Practice Using getElementsByClassName on GreatFrontEnd

9. Optimizing with Memoization

Memoization saves computed results to avoid redundant calculations.

function expensiveOperation(n) {
console.log('Calculating for', n);
return n * 2;
}
// Memoize function
function memoize(func) {
const cache = {};
return function (n) {
if (cache[n] !== undefined) {
console.log('From cache for', n);
return cache[n];
}
const result = func(n);
cache[n] = result;
return result;
};
}
const memoizedExpensiveOperation = memoize(expensiveOperation);
console.log(memoizedExpensiveOperation(5)); // Calculating for 5, 10
console.log(memoizedExpensiveOperation(5)); // From cache for 5, 10

Libraries like Lodash also provide a memoize utility.

Practice Memoization on GreatFrontEnd

10. Safer Nested Property Access: get

Accessing nested object properties risk errors if any property is undefined. Tools like Lodash's get or JavaScript's optional chaining (?.) help mitigate this.

const user = { address: { city: 'New York' } };
console.log(_.get(user, 'address.city')); // 'New York'
console.log(user.address?.city); // 'New York'

These methods safely retrieve nested properties without crashing the program.

Practice Using get on GreatFrontEnd

11. Hoisting in JavaScript

Hoisting refers to how JavaScript moves variable and function declarations to the top of their scope during compilation. While only the declaration is hoisted (not the initialization), understanding hoisting helps in writing cleaner and bug-free code.

Hoisting with var

Variables declared with var are hoisted and initialized as undefined. Accessing them before initialization results in undefined.

console.log(foo); // undefined
var foo = 1;
console.log(foo); // 1

Hoisting with let, const, and class

Variables declared with let, const, and class are hoisted but exist in a "temporal dead zone" until their declaration is reached, causing a ReferenceError if accessed early.

console.log(y); // ReferenceError
let y = 'local';

Function Hoisting

Function Declarations

Both the declaration and definition of functions are hoisted, allowing them to be called before their declaration.

foo(); // 'FOOOOO'
function foo() {
console.log('FOOOOO');
}

Function Expressions

For function expressions, only the variable is hoisted, not the function itself.

console.log(bar); // undefined
bar(); // TypeError: bar is not a function
var bar = function () {
console.log('BARRRR');
};

Import Hoisting

Imports are hoisted, making them available throughout the module. However, their initialization happens before the module code executes.

foo.doSomething(); // Works fine
import foo from './modules/foo';

Best Practices

Modern JavaScript uses let and const to avoid hoisting pitfalls. Declare variables at the top of their scope for better readability and use tools like ESLint to enforce best practices:

By following these practices, you can write robust, maintainable code.

Practice Hoisting on GreatFrontEnd

12. What are the differences between JavaScript variables created using let, var or const?

In JavaScript, let, var, and const are used to declare variables, but they differ in scope, initialization, redeclaration, reassignment, and behavior when accessed before declaration.

Scope

Variables declared with var are function-scoped or global, while let and const are block-scoped (confined to the nearest {} block).

if (true) {
var foo = 1;
let bar = 2;
const baz = 3;
}
console.log(foo); // 1
console.log(bar); // ReferenceError
console.log(baz); // ReferenceError

Initialization

var and let can be declared without initialization, but const requires an initial value.

var a; // Valid
let b; // Valid
const c; // SyntaxError: Missing initializer

Redeclaration

Variables declared with var can be redeclared, but let and const cannot.

var x = 10;
var x = 20; // Allowed
let y = 10;
let y = 20; // SyntaxError: Identifier 'y' has already been declared

Reassignment

var and let allow reassignment, while const does not.

let a = 1;
a = 2; // Allowed
const b = 1;
b = 2; // TypeError: Assignment to constant variable

Access Before Declaration

All variables are hoisted, but var initializes to undefined, whereas let and const exist in a "temporal dead zone" until the declaration is reached.

console.log(foo); // undefined
var foo = 'foo';
console.log(bar); // ReferenceError
let bar = 'bar';

Best Practices

  • Use const for variables that don’t change to ensure immutability.
  • Use let when reassignment is needed.
  • Avoid var due to its hoisting and scoping issues.
  • Use tools like ESLint to enforce modern best practices

Explore the difference between == and === in JavaScript on GreatFrontEnd

13. Explain the difference between == and === in JavaScript?

The == operator checks for equality after performing type conversion, while === checks for strict equality without type conversion.

Loose Equality (==)

== allows type coercion, which means JavaScript converts values to the same type before comparison. This can lead to unexpected results.

42 == '42'; // true
0 == false; // true
null == undefined; // true

Strict Equality (===)

=== checks both value and type, avoiding the pitfalls of type coercion.

42 === '42'; // false
0 === false; // false
null === undefined; // false

Use Case Tips

  • Prefer === for most comparisons as it avoids implicit type conversion and makes code more predictable.
  • Use == only when comparing null or undefined for simplicity.
let x = null;
console.log(x == null); // true
console.log(x == undefined); // true

Bonus: Object.is()

Object.is() is similar to === but treats -0 and +0 as distinct and considers NaN equal to itself.

console.log(Object.is(-0, +0)); // false
console.log(Object.is(NaN, NaN)); // true

Conclusion

  • Use === for strict comparisons to avoid bugs caused by type coercion.
  • Rely on Object.is() for nuanced comparisons like distinguishing -0 and +0.

Explore the difference between == and === in JavaScript on GreatFrontEnd

14. Understanding the Event Loop in JavaScript

The event loop is the backbone of JavaScript’s asynchronous behavior, enabling single-threaded execution without blocking.

Key Components

  1. Call Stack: Tracks function executions in a Last-In-First-Out (LIFO) order.
  2. Web APIs/Node.js APIs: Handle asynchronous tasks like setTimeout and HTTP requests on separate threads.
  3. Task Queue (Macrotask Queue): Queues tasks like setTimeout and UI events.
  4. Microtask Queue: Prioritizes tasks like Promise callbacks, executed before macrotasks.

How It Works

  1. Synchronous Code Execution: Functions are pushed and popped from the call stack.
  2. Asynchronous Tasks: Offloaded to APIs for processing.
  3. Task Completion: Completed tasks are queued.
  4. Event Loop Execution:
  • Executes microtasks until the queue is empty.
  • Processes one macrotask and checks the microtask queue again.

Example

console.log('Start');
setTimeout(() => console.log('Timeout 1'), 0);
Promise.resolve().then(() => console.log('Promise 1'));
setTimeout(() => console.log('Timeout 2'), 0);
console.log('End');

Output:

Start
End
Promise 1
Timeout 1
Timeout 2

Explanation:

  • Synchronous logs (Start, End) run first.
  • Microtasks (Promise 1) follow.
  • Macrotasks (Timeout 1, Timeout 2) run last.

Explore the event loop in JavaScript on GreatFrontEnd

15. What is Event Delegation in JavaScript?

Event delegation is an efficient way to manage events for multiple elements by attaching a single event listener to their common parent.

How It Works

  1. Attach a Listener: Add an event listener to a parent element instead of each child.
  2. Event Bubbling: Events triggered on children bubble up to the parent.
  3. Identify Target: Use event.target to determine the clicked element.
  4. Perform Action: Execute logic based on the event target.

Example

// HTML:
// <ul id="item-list">
// <li>Item 1</li>
// <li>Item 2</li>
// </ul>
const itemList = document.getElementById('item-list');
itemList.addEventListener('click', (event) => {
if (event.target.tagName === 'LI') {
console.log(`Clicked on ${event.target.textContent}`);
}
});

Benefits

  1. Efficiency: Reduces the number of event listeners, improving performance.
  2. Dynamic Content: Automatically handles new elements added to the DOM.

Explore event delegation in JavaScript on GreatFrontEnd

16. How this Works in JavaScript

The value of this depends on how a function is called. Let’s explore its different behaviors.

Scenarios

  1. Using new: When creating objects, this refers to the newly created object.

    function Person(name) {
    this.name = name;
    }
    const person = new Person('Alice');
    console.log(person.name); // 'Alice'
  2. Using apply, call, or bind: Explicitly sets this to a specified object.

    function greet() {
    console.log(this.name);
    }
    const person = { name: 'Alice' };
    greet.call(person); // 'Alice'
  3. Method Call: this refers to the object the method is called on.

    const obj = {
    name: 'Alice',
    greet() {
    console.log(this.name);
    },
    };
    obj.greet(); // 'Alice'
  4. Free Function Call: Defaults to the global object (window in browsers) or undefined in strict mode.

    function greet() {
    console.log(this); // global object or undefined
    }
    greet();
  5. Arrow Functions: Capture this from their enclosing scope.

    const obj = {
    name: 'Alice',
    greet: () => {
    console.log(this.name); // Inherits `this` from enclosing scope
    },
    };
    obj.greet(); // undefined

ES6 and this

Arrow functions simplify usage by capturing this from their lexical scope.

function Timer() {
this.seconds = 0;
setInterval(() => {
this.seconds++;
console.log(this.seconds);
}, 1000);
}
const timer = new Timer();

Explore how this works in JavaScript on GreatFrontEnd

17. What Sets Cookies, sessionStorage, and localStorage Apart?

When it comes to client-side storage, cookies, localStorage, and sessionStorage serve distinct roles:

Cookies

  • Function: Stores small pieces of data sent along with HTTP requests to the server.

  • Limit: Roughly 4KB per domain.

  • Lifetime: Can persist or expire after a set time. Session cookies disappear when the browser closes.

  • Scope: Accessible across pages and subdomains for a single domain.

  • Security: Features like HttpOnly and Secure flags add extra security.

  • Example:

    // Set a cookie with an expiry date
    document.cookie =
    'userId=12345; expires=Fri, 31 Dec 2025 23:59:59 GMT; path=/';
    // Read all cookies
    console.log(document.cookie);
    // Delete a cookie
    document.cookie = 'userId=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/';

localStorage

  • Function: Allows persistent data storage on the client side.

  • Limit: About 5MB per origin.

  • Lifetime: Data stays until explicitly removed.

  • Scope: Shared across all tabs and windows for the same origin.

  • Security: Accessible by JavaScript within the same origin.

  • Example:

    // Store data in localStorage
    localStorage.setItem('username', 'john_doe');
    // Retrieve data
    console.log(localStorage.getItem('username'));
    // Remove an item
    localStorage.removeItem('username');
    // Clear all localStorage data
    localStorage.clear();

sessionStorage

  • Function: Stores data for the duration of a page session.

  • Limit: Similar to localStorage (around 5MB).

  • Lifetime: Cleared when the tab or browser closes.

  • Scope: Data is confined to the current tab or window.

  • Security: Accessible by JavaScript on the same origin.

  • Example:

    // Store data in sessionStorage
    sessionStorage.setItem('sessionId', 'abcdef');
    // Retrieve data
    console.log(sessionStorage.getItem('sessionId'));
    // Remove an item
    sessionStorage.removeItem('sessionId');
    // Clear all sessionStorage data
    sessionStorage.clear();

Learn more about cookies, sessionStorage, and localStorage on GreatFrontEnd

18. How Do <script>, <script async>, and <script defer> Differ?

The Basic <script> Tag

When using the <script> tag without attributes, it fetches and executes the script immediately, pausing HTML parsing.

  • Use Case: Critical scripts needed before page rendering.

  • Example:

    <script src="main.js"></script>

The <script async> Tag

With async, the script loads in parallel to HTML parsing and executes as soon as it’s ready.

  • Use Case: Independent scripts like analytics or ads.
  • Example:
    <script async src="analytics.js"></script>

The <script defer> Tag

When using defer, the script loads alongside HTML parsing but only executes after the HTML is fully parsed.

  • Use Case: Scripts that rely on a complete DOM structure.

  • Example:

    <script defer src="deferred.js"></script>

Discover more about <script>, <script async>, and <script defer> on GreatFrontEnd

19. null, undefined, or Undeclared: What’s the Difference?

Undeclared

Variables not defined using var, let, or const are considered undeclared and can cause global scope issues.

undefined

A declared variable that hasn’t been assigned a value is undefined.

null

Represents the intentional absence of any value. It’s an explicit assignment. Example Code:

let a;
console.log(a); // undefined
let b = null;
console.log(b); // null
try {
console.log(c); // ReferenceError: c is not defined
} catch (e) {
console.log('c is undeclared');
}

Read more about null, undefined, and undeclared variables on GreatFrontEnd

20. .call vs .apply: What’s the Difference?

Both .call and .apply let you invoke a function with a specified this value. The key difference lies in how arguments are passed:

  • .call: Accepts arguments as a comma-separated list.
  • .apply: Accepts arguments as an array.

Memory Aid:

  • C for call = comma-separated
  • A for apply = array

Example Code:

function sum(a, b) {
return a + b;
}
console.log(sum.call(null, 1, 2)); // 3
console.log(sum.apply(null, [1, 2])); // 3

Learn more about .call and .apply on GreatFrontEnd

21. How Does Function.prototype.bind Work?

The bind method is used to create a new function with a specific this value and, optionally, preset arguments. This ensures that the function always has the correct this context, regardless of how or where it’s called.

Key Uses of bind:

  1. Maintaining Context: Ensures that this is correctly set for the function.
  2. Preset Arguments: Allows you to predefine arguments for a function.
  3. Borrowing Methods: Enables you to use methods from one object in another.

Example:

const john = {
age: 42,
getAge: function () {
return this.age;
},
};
console.log(john.getAge()); // 42
const unboundGetAge = john.getAge;
console.log(unboundGetAge()); // undefined
const boundGetAge = john.getAge.bind(john);
console.log(boundGetAge()); // 42
const mary = { age: 21 };
const boundGetAgeMary = john.getAge.bind(mary);
console.log(boundGetAgeMary()); // 21

Explore Function.prototype.bind on GreatFrontEnd

22. Why Use Arrow Functions for Methods in Constructors?

Using arrow functions for methods in constructors automatically binds the this context to the constructor, avoiding the need to manually bind it. This eliminates issues caused by this referring to unexpected contexts.

Example:

const Person = function (name) {
this.name = name;
this.sayName1 = function () {
console.log(this.name);
};
this.sayName2 = () => {
console.log(this.name);
};
};
const john = new Person('John');
const dave = new Person('Dave');
john.sayName1(); // John
john.sayName2(); // John
john.sayName1.call(dave); // Dave
john.sayName2.call(dave); // John

Use Case in React:

Arrow functions are particularly useful in React class components, ensuring methods maintain the correct context when passed to child components.

Explore the advantage for using the arrow syntax for a method in a constructor on GreatFrontEnd

23. How Does Prototypal Inheritance Work?

Prototypal inheritance is a way for objects to share properties and methods through their prototype chain.

Key Concepts:

  1. Prototypes: Each object has a prototype, from which it inherits properties and methods.
  2. Prototype Chain: JavaScript looks for properties/methods up the chain until it finds them or reaches null.
  3. Constructor Functions: Functions used with new to create objects.

Example:

function Animal(name) {
this.name = name;
}
Animal.prototype.sayName = function () {
console.log(`My name is ${this.name}`);
};
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.bark = function () {
console.log('Woof!');
};
let fido = new Dog('Fido', 'Labrador');
fido.bark(); // "Woof!"
fido.sayName(); // "My name is Fido"

Explore how prototypal inheritance works on GreatFrontEnd

24. Differences Between: function Person(){}, const person = Person(), and const person = new Person()?

Key Differences:

  1. function Person(){}: A function declaration, typically used for constructors if written in PascalCase.
  2. const person = Person(): Calls the function normally and assigns the result to person. No object creation happens unless explicitly returned.
  3. const person = new Person(): Invokes the function as a constructor, creating a new object and setting its prototype to Person.prototype.

Explore the difference between: function Person(){}, const person = Person(), and const person = new Person() on GreatFrontEnd

25. Function Declarations vs. Function Expressions: What's the Difference?

Function Declarations:

  • Syntax: function foo() {}

  • Hoisting: Fully hoisted; can be called before its definition.

  • Example:

    foo(); // "Hello!"
    function foo() {
    console.log('Hello!');
    }

Function Expressions:

  • Syntax: var foo = function() {}

  • Hoisting: Only the variable is hoisted, not the function body.

  • Example:

    foo(); // TypeError: foo is not a function
    var foo = function () {
    console.log('Hello!');
    };

Explore the differences on the usage of foo between function foo() {} and var foo = function() {} on GreatFrontEnd

26. What Are the Different Ways to Create Objects in JavaScript?

Here are various approaches to creating objects in JavaScript:

  1. Object Literals The simplest and most common way to create an object is using curly braces {} with key-value pairs.

    const person = {
    firstName: 'John',
    lastName: 'Doe',
    };
  2. Object Constructor Use the built-in Object constructor with the new keyword.

    const person = new Object();
    person.firstName = 'John';
    person.lastName = 'Doe';
  3. Object.create() Method Create an object with a specific prototype.

    const personPrototype = {
    greet() {
    console.log(`Hello, my name is ${this.name}.`);
    },
    };
    const person = Object.create(personPrototype);
    person.name = 'John';
    person.greet(); // Hello, my name is John.
  4. ES2015 Classes Define objects using the class syntax for a blueprint-like structure.

    class Person {
    constructor(name, age) {
    this.name = name;
    this.age = age;
    }
    greet() {
    console.log(`Hi, I’m ${this.name} and I’m ${this.age} years old.`);
    }
    }
    const john = new Person('John', 30);
    john.greet(); // Hi, I’m John and I’m 30 years old.
  5. Constructor Functions Use a function as a template for creating multiple objects.

    function Person(name, age) {
    this.name = name;
    this.age = age;
    }
    const john = new Person('John', 30);
    console.log(john.name); // John

Explore various ways to create objects in JavaScript on GreatFrontEnd

27. What Is a Higher-Order Function?

A higher-order function is a function that either:

  1. Accepts another function as an argument Example:

    function greet(name) {
    return `Hello, ${name}!`;
    }
    function greetUser(greeter, name) {
    console.log(greeter(name));
    }
    greetUser(greet, 'Alice'); // Hello, Alice!
  2. Returns another function Example:

    function multiplier(factor) {
    return function (num) {
    return num * factor;
    };
    }
    const double = multiplier(2);
    console.log(double(4)); // 8

Explore the definition of a higher-order function on GreatFrontEnd

28. How Do ES2015 Classes Differ from ES5 Constructor Functions?

ES5 Constructor Functions

  • Use function constructors and prototypes for object creation and inheritance.
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function () {
console.log(`Hi, I’m ${this.name} and I’m ${this.age} years old.`);
};
const john = new Person('John', 30);
john.greet(); // Hi, I’m John and I’m 30 years old.

ES2015 Classes

  • Use the class keyword for cleaner and more intuitive syntax.
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hi, I’m ${this.name} and I’m ${this.age} years old.`);
}
}
const john = new Person('John', 30);
john.greet(); // Hi, I’m John and I’m 30 years old.

Key Differences:

  • Syntax: ES2015 classes are more readable and concise.
  • Static Methods: Easier to define using static in ES2015.
  • Inheritance: Simpler with the extends and super keywords in ES2015.

Explore differences between ES2015 classes and ES5 constructor functions on GreatFrontEnd

29. What Is Event Bubbling?

Event bubbling is the process where an event triggers on the target element and then propagates upwards through its ancestors in the DOM.

Example:

const parent = document.getElementById('parent');
const child = document.getElementById('child');
parent.addEventListener('click', () => {
console.log('Parent clicked');
});
child.addEventListener('click', () => {
console.log('Child clicked');
});

Clicking the child element will log both "Child clicked" and "Parent clicked" due to bubbling.

Prevent Bubbling:

Use stopPropagation() to prevent the event from propagating upwards.

child.addEventListener('click', (event) => {
event.stopPropagation();
console.log('Child clicked only');
});

Explore event bubbling on GreatFrontEnd

30. What Is Event Capturing?

Event capturing, also called "trickling," is the reverse of bubbling. The event propagates from the root element down to the target element.

Enable Capturing:

Capturing is enabled by passing { capture: true } in addEventListener().

const parent = document.getElementById('parent');
const child = document.getElementById('child');
parent.addEventListener(
'click',
() => {
console.log('Parent capturing');
},
{ capture: true },
);
child.addEventListener('click', () => {
console.log('Child clicked');
});

Clicking the child will log "Parent capturing" first, followed by "Child clicked."

Explore event capturing on GreatFrontEnd

31. How Do mouseenter and mouseover Differ?

mouseenter

  • Does not bubble up the DOM tree.
  • Triggered only when the mouse pointer enters the element itself, excluding its children.
  • Fires a single event when entering the target element.

mouseover

  • Bubbles up the DOM tree.
  • Triggered when the mouse pointer enters the target element or any of its children.
  • Fires multiple events when moving over child elements.

Explore the difference between mouseenter and mouseover on GreatFrontEnd

32. Synchronous vs. Asynchronous Functions: What's the Difference?

Synchronous Functions

  • Execute tasks in a sequential, blocking manner.
  • Program execution halts until the current task completes.
  • Easier to debug due to their predictable flow.

Example:

const fs = require('fs');
const data = fs.readFileSync('file.txt', 'utf8');
console.log(data); // Blocks until the file is fully read
console.log('Program ends');

Asynchronous Functions

  • Perform tasks without blocking program execution.
  • Other operations can run while waiting for the task to finish.
  • Commonly used for I/O operations, network requests, and timers.

Example:

console.log('Start');
fetch('https://api.example.com/data')
.then((response) => response.json())
.then((data) => console.log(data)) // Non-blocking
.catch((error) => console.error(error));
console.log('End');

Explore the difference between synchronous and asynchronous functions on GreatFrontEnd

33. What Is AJAX?

AJAX (Asynchronous JavaScript and XML) is a technique that allows web pages to fetch and send data asynchronously, enabling dynamic updates without reloading the entire page.

Key Points

  • Asynchronous: Updates parts of a page without reloading.
  • Data Formats: Initially XML, now primarily JSON due to its simplicity.
  • APIs: Traditionally used XMLHttpRequest; fetch() is the modern alternative.

Using XMLHttpRequest:

let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
console.log(xhr.responseText);
} else {
console.error('Request failed');
}
}
};
xhr.open('GET', 'https://jsonplaceholder.typicode.com/todos/1', true);
xhr.send();

Using fetch():

fetch('https://jsonplaceholder.typicode.com/todos/1')
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.error('Fetch error:', error));

Explore AJAX in detail on GreatFrontEnd

34. Pros and Cons of Using AJAX

Advantages

  • Enhances user experience by enabling seamless updates.
  • Reduces server load by fetching only necessary data.
  • Keeps the user on the same page while updating content.

Disadvantages

  • Relies on JavaScript, so functionality may break if it’s disabled.
  • SEO challenges with dynamically loaded content.
  • Bookmarking specific page states becomes difficult.

Explore the advantages and disadvantages of using AJAX on GreatFrontEnd

35. Comparing XMLHttpRequest and fetch()

XMLHttpRequest

  • Syntax: Event-driven; requires listeners for response handling.
  • Progress Tracking: Supports progress tracking via onprogress.
  • Error Handling: Uses onerror event.

Example:

let xhr = new XMLHttpRequest();
xhr.open('GET', 'https://example.com/api', true);
xhr.onload = function () {
if (xhr.status === 200) {
console.log(xhr.responseText);
}
};
xhr.send();

fetch()

  • Syntax: Promise-based; simpler and more readable.
  • Error Handling: Uses .catch() for better error management.
  • Modern Features: Built-in support for AbortController for cancellations.

Example:

fetch('https://example.com/api')
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.error(error));

Key Differences

  • fetch() has cleaner syntax and better Promise integration.
  • XMLHttpRequest supports progress tracking, which fetch() does not.

Explore the differences between XMLHttpRequest and fetch() on GreatFrontEnd

36. What Are the Data Types in JavaScript?

JavaScript features a mix of primitive and non-primitive (reference) data types.

Primitive Data Types

  • Number: Includes integers and floating-point values.
  • String: Text values enclosed in single, double quotes, or backticks.
  • Boolean: Represents true or false.
  • Undefined: A declared variable that hasn’t been assigned a value.
  • Null: Indicates an intentional lack of value.
  • Symbol: A unique and immutable identifier often used as object property keys.
  • BigInt: Handles large integers with arbitrary precision.

Non-Primitive Data Types

  • Object: Collections of key-value pairs.
  • Array: Ordered lists of elements.
  • Function: First-class objects that can be assigned, passed, and returned.
  • Date: Represents date and time values.
  • RegExp: For pattern matching in strings.
  • Map: A collection of key-value pairs, allowing any type of key.
  • Set: Stores unique values, whether primitive or object references.

Type Detection: Use the typeof operator to determine the type of a variable.

Explore the various data types in JavaScript on GreatFrontEnd

37. How Do You Iterate Over Object Properties and Array Items?

JavaScript provides multiple ways to iterate over objects and arrays.

Iterating Over Objects

1. for...in

Loops over all enumerable properties, including inherited ones.

for (const property in obj) {
if (Object.hasOwn(obj, property)) {
console.log(property);
}
}

2. Object.keys()

Retrieves an array of an object’s own enumerable properties.

Object.keys(obj).forEach((key) => console.log(key));

3. Object.entries()

Returns an array of [key, value] pairs.

Object.entries(obj).forEach(([key, value]) => console.log(`${key}: ${value}`));

4. Object.getOwnPropertyNames()

Includes both enumerable and non-enumerable properties.

Object.getOwnPropertyNames(obj).forEach((prop) => console.log(prop));

Iterating Over Arrays

1. for Loop

Classic approach for iterating through arrays:

for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}

2. Array.prototype.forEach()

Executes a callback for each array item.

arr.forEach((element, index) => console.log(element, index));

3. for...of

Ideal for looping through iterable objects like arrays.

for (const element of arr) {
console.log(element);
}

4. Array.prototype.entries()

Iterates with both index and value.

for (const [index, element] of arr.entries()) {
console.log(index, ':', element);
}

Explore iteration techniques on GreatFrontEnd

38. What Are the Benefits of Spread Syntax, and How Is It Different from Rest Syntax?

Spread Syntax (...)

The spread operator is used to expand elements of arrays or objects.

  • Copying Arrays/Objects:

    const array = [1, 2, 3];
    const newArray = [...array]; // [1, 2, 3]
  • Merging Arrays/Objects:

    const arr1 = [1, 2];
    const arr2 = [3, 4];
    const mergedArray = [...arr1, ...arr2]; // [1, 2, 3, 4]
  • Passing Function Arguments:

    const nums = [1, 2, 3];
    console.log(Math.max(...nums)); // 3

Rest Syntax (...)

The rest operator collects multiple elements into an array or object.

  • Function Parameters:

    function sum(...numbers) {
    return numbers.reduce((a, b) => a + b);
    }
    sum(1, 2, 3); // 6
  • Destructuring:

    const [first, ...rest] = [1, 2, 3];
    console.log(rest); // [2, 3]

Explore spread and rest syntax on GreatFrontEnd

39. Map vs. Plain Object: What's the Difference?

Map

  • Keys can be any type.
  • Maintains the insertion order.
  • Has a size property.
  • Directly iterable.
const map = new Map();
map.set('key', 'value');
console.log(map.size); // 1

Plain Object

  • Keys are strings or symbols.
  • Iteration requires Object.keys(), Object.values(), or Object.entries().
  • No direct size property.
const obj = { key: 'value' };
console.log(Object.keys(obj).length); // 1

Explore the difference between Map and plain objects on GreatFrontEnd

40. Differences Between Map/Set and WeakMap/WeakSet

Key Differences

  • Key Types: WeakMap and WeakSet keys must be objects, while Map and Set accept any data type.
  • Memory Management: WeakMap and WeakSet allow garbage collection of keys, making them useful for managing memory.
  • Size: Only Map and Set have a size property.
  • Iteration: WeakMap and WeakSet are not iterable.

Example

// Map Example
const map = new Map();
map.set({}, 'value');
console.log(map.size); // 1
// WeakMap Example
const weakMap = new WeakMap();
let obj = {};
weakMap.set(obj, 'value');
obj = null; // Key is garbage-collected

Explore the differences between Map/Set and WeakMap/WeakSet on GreatFrontEnd

41. What Are Practical Use Cases for Arrow Functions?

Arrow functions simplify function syntax, making them ideal for inline callbacks.

Example: Mapping an Array

// Traditional function syntax
const numbers = [1, 2, 3, 4, 5];
const doubledNumbers = numbers.map(function (number) {
return number * 2;
});
console.log(doubledNumbers); // [2, 4, 6, 8, 10]
// Arrow function syntax
const doubledWithArrow = numbers.map((number) => number * 2);
console.log(doubledWithArrow); // [2, 4, 6, 8, 10]

Explore a use case for the new arrow function syntax on GreatFrontEnd

42. What Are Callback Functions in Asynchronous Operations?

A callback is a function passed as an argument to another function, executed after the completion of an asynchronous task.

Example

function fetchData(callback) {
setTimeout(() => {
const data = { name: 'John', age: 30 };
callback(data);
}, 1000);
}
fetchData((data) => {
console.log(data); // { name: 'John', age: 30 }
});

Explore the concept of a callback function in asynchronous operations on GreatFrontEnd

43. What Are Debouncing and Throttling?

Debouncing

Delays execution of a function until a specified time has elapsed since its last invocation.

function debounce(func, delay) {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}

Throttling

Ensures a function executes at most once within a set time interval.

function throttle(func, limit) {
let inThrottle;
return (...args) => {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}

Explore the concept of debouncing and throttling on GreatFrontEnd

44. How Does Destructuring Assignment Work?

Destructuring simplifies extracting values from arrays or objects into individual variables.

Example

// Array destructuring
const [a, b] = [1, 2];
// Object destructuring
const { name, age } = { name: 'John', age: 30 };

Explore the concept of destructuring assignment on GreatFrontEnd

45. What Is Function Hoisting?

Hoisting moves function declarations to the top of their scope during the compilation phase. However, function expressions and arrow functions do not get hoisted in the same way.

Example

// Function declaration
hoistedFunction(); // Works fine
function hoistedFunction() {
console.log('This function is hoisted');
}
// Function expression
nonHoistedFunction(); // Throws an error
var nonHoistedFunction = function () {
console.log('This function is not hoisted');
};

Explore the concept of hoisting on GreatFrontEnd

46. How Does Inheritance Work in ES2015 Classes?

Classes in ES2015 use extends for inheritance and super to access parent constructors and methods.

Example

class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
speak() {
console.log(`${this.name} barks.`);
}
}
const dog = new Dog('Rex', 'German Shepherd');
dog.speak(); // Rex barks.

Explore the concept of inheritance in ES2015 classes on GreatFrontEnd

47. What Is Lexical Scoping?

Lexical scoping determines variable access based on where functions are defined, not where they’re called.

Example

function outerFunction() {
let outerVariable = 'I am outside!';
function innerFunction() {
console.log(outerVariable); // I am outside!
}
innerFunction();
}
outerFunction();

Explore the concept of lexical scoping on GreatFrontEnd

48. What Are Scopes in JavaScript?

JavaScript has three main types of scope: global, function, and block.

Example

// Global scope
var globalVar = 'I am global';
function myFunction() {
// Function scope
var functionVar = 'I am in a function';
if (true) {
// Block scope
let blockVar = 'I am in a block';
console.log(blockVar); // Accessible here
}
// console.log(blockVar); // Error
}

Explore the concept of scope in JavaScript on GreatFrontEnd

49. What Is the Spread Operator?

The spread operator (...) expands elements of an iterable (like arrays) or properties of objects into individual elements.

Examples

// Copying an array
const arr1 = [1, 2, 3];
const arr2 = [...arr1];
// Merging arrays
const mergedArray = [...arr1, [4, 5]];
// Copying an object
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1 };
// Passing as function arguments
const sum = (x, y, z) => x + y + z;
const nums = [1, 2, 3];
sum(...nums); // 6

Explore the spread operator on GreatFrontEnd

50. How Does this Work in Event Handlers?

In JavaScript, this in event handlers refers to the element that triggered the event. Its context can be explicitly bound using bind(), arrow functions, or direct assignment.

Example

const button = document.querySelector('button');
button.addEventListener('click', function () {
console.log(this); // Refers to the button
});
const obj = {
handleClick: function () {
console.log(this); // Refers to obj
},
};
button.addEventListener('click', obj.handleClick.bind(obj));

Explore the concept of this in event handlers on GreatFrontEnd

Conclusion

Congratulations on reaching the end of our comprehensive collection of JavaScript interview questions and answers! We hope this guide has equipped you with the knowledge and confidence needed to excel in your next JavaScript interview. Keep in mind that consistent practice is essential—continue coding and revisiting these concepts until they feel effortless.