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(() => {
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) => {
console.log('All responses:', responses);
})
.catch((error) => {
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;
}
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));
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();
eventEmitter.on('customEvent', (data) => {
console.log('Event emitted with data:', data);
});
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);
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.
const nestedArray = [1, [2, [3, [4, [5]]]]];
const flatArray = nestedArray.flat(Infinity);
console.log(flatArray);
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:
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);
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);
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);
Merging Arrays
Using the Spread Operator
const array1 = [1, 2, 3];
const array2 = [4, 5, 6];
const mergedArray = [...array1, ...array2];
console.log(mergedArray);
Using Array.concat()
const array1 = [1, 2, 3];
const array2 = [4, 5, 6];
const mergedArray = array1.concat(array2);
console.log(mergedArray);
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);
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);
Practice Merging Data on GreatFrontEnd
8. Selecting DOM Elements: getElementsByClassName
getElementsByClassName
fetches elements matching a specific class and returns them as a live HTMLCollection
.
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;
}
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));
console.log(memoizedExpensiveOperation(5));
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'));
console.log(user.address?.city);
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);
var foo = 1;
console.log(foo);
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);
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();
function foo() {
console.log('FOOOOO');
}
Function Expressions
For function expressions, only the variable is hoisted, not the function itself.
console.log(bar);
bar();
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();
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);
console.log(bar);
console.log(baz);
Initialization
var
and let
can be declared without initialization, but const
requires an initial value.
Redeclaration
Variables declared with var
can be redeclared, but let
and const
cannot.
var x = 10;
var x = 20;
let y = 10;
let y = 20;
Reassignment
var
and let
allow reassignment, while const
does not.
let a = 1;
a = 2;
const b = 1;
b = 2;
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);
var foo = 'foo';
console.log(bar);
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';
0 == false;
null == undefined;
Strict Equality (===
)
===
checks both value and type, avoiding the pitfalls of type coercion.
42 === '42';
0 === false;
null === undefined;
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);
console.log(x == undefined);
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));
console.log(Object.is(NaN, NaN));
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
- Call Stack: Tracks function executions in a Last-In-First-Out (LIFO) order.
- Web APIs/Node.js APIs: Handle asynchronous tasks like
setTimeout
and HTTP requests on separate threads.
- Task Queue (Macrotask Queue): Queues tasks like
setTimeout
and UI events.
- Microtask Queue: Prioritizes tasks like
Promise
callbacks, executed before macrotasks.
How It Works
- Synchronous Code Execution: Functions are pushed and popped from the call stack.
- Asynchronous Tasks: Offloaded to APIs for processing.
- Task Completion: Completed tasks are queued.
- 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
- Attach a Listener: Add an event listener to a parent element instead of each child.
- Event Bubbling: Events triggered on children bubble up to the parent.
- Identify Target: Use
event.target
to determine the clicked element.
- Perform Action: Execute logic based on the event target.
Example
const itemList = document.getElementById('item-list');
itemList.addEventListener('click', (event) => {
if (event.target.tagName === 'LI') {
console.log(`Clicked on ${event.target.textContent}`);
}
});
Benefits
- Efficiency: Reduces the number of event listeners, improving performance.
- 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
-
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);
-
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);
-
Method Call: this
refers to the object the method is called on.
const obj = {
name: 'Alice',
greet() {
console.log(this.name);
},
};
obj.greet();
-
Free Function Call: Defaults to the global object (window
in browsers) or undefined
in strict mode.
function greet() {
console.log(this);
}
greet();
-
Arrow Functions: Capture this
from their enclosing scope.
const obj = {
name: 'Alice',
greet: () => {
console.log(this.name);
},
};
obj.greet();
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:
document.cookie =
'userId=12345; expires=Fri, 31 Dec 2025 23:59:59 GMT; path=/';
console.log(document.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:
localStorage.setItem('username', 'john_doe');
console.log(localStorage.getItem('username'));
localStorage.removeItem('username');
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:
sessionStorage.setItem('sessionId', 'abcdef');
console.log(sessionStorage.getItem('sessionId'));
sessionStorage.removeItem('sessionId');
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.
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.
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);
let b = null;
console.log(b);
try {
console.log(c);
} 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));
console.log(sum.apply(null, [1, 2]));
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
:
- Maintaining Context: Ensures that
this
is correctly set for the function.
- Preset Arguments: Allows you to predefine arguments for a function.
- 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());
const unboundGetAge = john.getAge;
console.log(unboundGetAge());
const boundGetAge = john.getAge.bind(john);
console.log(boundGetAge());
const mary = { age: 21 };
const boundGetAgeMary = john.getAge.bind(mary);
console.log(boundGetAgeMary());
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.sayName2();
john.sayName1.call(dave);
john.sayName2.call(dave);
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:
- Prototypes: Each object has a prototype, from which it inherits properties and methods.
- Prototype Chain: JavaScript looks for properties/methods up the chain until it finds them or reaches
null
.
- 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();
fido.sayName();
Explore how prototypal inheritance works on GreatFrontEnd
24. Differences Between: function Person(){}
, const person = Person()
, and const person = new Person()
?
Key Differences:
function Person(){}
: A function declaration, typically used for constructors if written in PascalCase.
const person = Person()
: Calls the function normally and assigns the result to person
. No object creation happens unless explicitly returned.
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:
Function Expressions:
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:
-
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',
};
-
Object
Constructor Use the built-in Object
constructor with the new
keyword.
const person = new Object();
person.firstName = 'John';
person.lastName = 'Doe';
-
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();
-
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();
-
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);
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:
-
Accepts another function as an argument Example:
function greet(name) {
return `Hello, ${name}!`;
}
function greetUser(greeter, name) {
console.log(greeter(name));
}
greetUser(greet, 'Alice');
-
Returns another function Example:
function multiplier(factor) {
return function (num) {
return num * factor;
};
}
const double = multiplier(2);
console.log(double(4));
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();
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();
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);
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))
.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.
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);
-
Destructuring:
const [first, ...rest] = [1, 2, 3];
console.log(rest);
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);
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);
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
const map = new Map();
map.set({}, 'value');
console.log(map.size);
const weakMap = new WeakMap();
let obj = {};
weakMap.set(obj, 'value');
obj = null;
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
const numbers = [1, 2, 3, 4, 5];
const doubledNumbers = numbers.map(function (number) {
return number * 2;
});
console.log(doubledNumbers);
const doubledWithArrow = numbers.map((number) => number * 2);
console.log(doubledWithArrow);
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);
});
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
const [a, b] = [1, 2];
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
hoistedFunction();
function hoistedFunction() {
console.log('This function is hoisted');
}
nonHoistedFunction();
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();
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);
}
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
var globalVar = 'I am global';
function myFunction() {
var functionVar = 'I am in a function';
if (true) {
let blockVar = 'I am in a block';
console.log(blockVar);
}
}
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
const arr1 = [1, 2, 3];
const arr2 = [...arr1];
const mergedArray = [...arr1, [4, 5]];
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1 };
const sum = (x, y, z) => x + y + z;
const nums = [1, 2, 3];
sum(...nums);
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);
});
const obj = {
handleClick: function () {
console.log(this);
},
};
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.