10 JavaScript Advanced Interview Questions
In the realm of web development, advanced JavaScript knowledge remains paramount. Not only does it equip developers with the tools to tackle complex problems, but it also helps in understanding the intricacies of the language that powers most of the web. These advanced questions reflect not just theoretical knowledge, but also the practical challenges and nuances developers encounter in the real world. At codedamn, we believe that mastering these concepts is key to standing out in interviews and executing projects with excellence.
Closures
Question: Explain what a closure is and provide an example where they might be useful.
Expected Answer
A closure in JavaScript is a function that retains access to its outer (parent) scope’s variables, even after the parent function has executed and closed. This behavior allows for data privacy and the creation of factory functions.
For example, closures can be used to create private variables:
1function outerFunction() {
2 let count = 0; // This variable is outside of the innerFunction scope but can be accessed because of closure.
3
4 function innerFunction() {
5 count++;
6 console.log(count);
7 }
8
9 return innerFunction;
10}
11
12const counter = outerFunction();
13counter(); // Outputs: 1
14counter(); // Outputs: 2
In the above code, every time counter
is called, it increments the count
variable, despite outerFunction
having already executed.
Promises and Async/Await
Question: How do Promises work and how does Async/Await enhance them?
Expected Answer
Promises are a way in JavaScript to handle asynchronous operations without falling into the “callback hell”. A Promise has three states: pending, fulfilled (resolved), and rejected. Once a Promise is either fulfilled or rejected, it becomes immutable (its state cannot change).
The primary advantage of using Async/Await over raw Promises is the ability to write asynchronous code that looks and behaves more like synchronous code. This can make the code more readable and easier to understand.
For instance, without Async/Await:
1function fetchData() {
2 return new Promise((resolve) => {
3 setTimeout(() => {
4 resolve('Data fetched!');
5 }, 2000);
6 });
7}
8
9fetchData().then(result => {
10 console.log(result); // Outputs: Data fetched!
11});
With Async/Await:
async function displayData() {
const data = await fetchData();
console.log(data); // Outputs: Data fetched!
}
displayData();
By using async/await
, we can avoid the .then
chaining, leading to cleaner code. More about Promises can be found on MDN’s Official Documentation.
The “this” Keyword
Question: How does the “this” keyword work in JavaScript? Provide examples.
Expected Answer
In JavaScript, the value of this
is determined by how a function is called. It’s not assigned a value until the function is actually called.
- Global context: Outside of any function,
this
refers to the global object (window
in a browser,global
in Node.js). - Object methods: When a function is called as a method of an object,
this
is set to the object. - Event handlers: In event handlers,
this
refers to the element that received the event. - Constructors: When a function is used as a constructor (with the
new
keyword),this
is bound to the resulting instance object. - Arrow functions: Arrow functions don’t have their own
this
. They inherit thethis
value of the enclosing scope.
Examples:
1// Global context
2console.log(this === window); // Outputs: true in browsers
3
4// Object method
5const obj = {
6 method: function() {
7 console.log(this === obj); // Outputs: true
8 }
9};
10obj.method();
11
12// Event handlers (in browsers)
13button.addEventListener('click', function() {
14 console.log(this === button); // Outputs: true if 'button' is the clicked element
15});
Further details on the “this” keyword can be found at MDN’s Official Documentation.
Prototypal Inheritance
Question: Describe how inheritance works in JavaScript.
Expected Answer
Inheritance in JavaScript is based on prototypes. Every object in JavaScript has a prototype, from which it inherits properties and methods. When trying to access a property or method on an object, if it’s not found on the actual object, the JavaScript engine looks up its prototype chain until it finds it or reaches an object with a null prototype.
Example:
1function Dog(name) {
2 this.name = name;
3}
4
5Dog.prototype.bark = function() {
6 console.log(`${this.name} says woof!`);
7};
8
9const myDog = new Dog('Buddy');
10myDog.bark(); // Outputs: Buddy says woof!
11
12console.log(myDog.hasOwnProperty('bark')); // Outputs: false, since bark is on Dog's prototype, not on myDog itself
In the above example, myDog
inherits the bark
method from Dog.prototype
.
The concept of prototypal inheritance can be explored further in MDN’s Official Documentation.
Event Loop and Concurrency Model
Question: Explain the JavaScript event loop and its importance.
Expected Answer
JavaScript is single-threaded, which means it executes code in a single sequence. Despite being single-threaded, JavaScript can perform non-blocking operations like timeouts, AJAX calls, and other asynchronous tasks. This is made possible by the event loop in conjunction with the call stack and the task queue.
- Call Stack: It’s a data structure that keeps track of functions currently being executed. If a function is called, it’s pushed onto the stack. When it returns, it’s popped off.
- Task Queue (or Callback Queue): When asynchronous operations finish executing, their callback functions are pushed to the task queue.
- Event Loop: Its primary role is to check if the call stack is empty. If it is, it dequeues a function from the task queue and pushes it onto the call stack to execute.
This mechanism ensures that the main thread (or call stack) remains non-blocked, allowing for a responsive environment, especially vital for tasks like UI rendering and user interactions.
A more in-depth exploration of the event loop can be found at MDN’s Official Documentation.
Variable Hoisting
Question: What is variable hoisting and how does it impact the order of operations in a program?
Expected Answer
Variable hoisting is a behavior in JavaScript where variable and function declarations are moved to the top of their containing scope during the compilation phase, before the code is executed. This means that variables can be used before they are declared in the code.
For instance, consider the following code:
console.log(x);
var x = 5;
Even though it appears as if we are using x
before it’s declared, the above code won’t result in a ReferenceError. Instead, it logs undefined
because of hoisting. The JavaScript interpreter actually sees the code as:
var x;
console.log(x);
x = 5;
However, this behavior is unique to var
declarations. ES6 let
and const
are hoisted as well but they don’t get initialized with undefined
, which leads to a temporal dead zone. Thus, trying to access them before their declaration will throw an error.
It’s worth noting that only declarations are hoisted, not initializations.
Official MDN documentation on Hoisting
Deep vs Shallow Copy
Question: Explain the difference between deep and shallow copy. How would you implement each in JavaScript?
Expected Answer
A shallow copy of an object is a copy of the object itself, but not the objects it references. If the original object has references to other objects, the shallow copy will still point to the same objects as the original.
A deep copy, on the other hand, creates duplicates of every item/object referenced by the original object, recursively. This means changes to the deep-copied object don’t affect the original object and vice-versa.
In JavaScript, shallow copy can be achieved using methods like Object.assign()
or the spread operator:
let obj1 = { a: 1, b: { c: 2 } };
let shallowCopy = { ...obj1 };
Deep copying can be more tricky, and might be achieved using methods like JSON.parse(JSON.stringify(obj))
for plain objects, but this has limitations (doesn’t work with functions, undefined
, special objects like RegExp, Map, Set, etc.). For complete deep copy, libraries like lodash’s _.cloneDeep()
are often used.
Official MDN documentation on Object.assign
Memory Leaks
Question: What are memory leaks in JavaScript? How can they be detected and prevented?
Expected Answer
Memory leaks occur when objects are no longer needed by the application but are still retained in memory because they’re referenced elsewhere, preventing garbage collection. Common causes include forgotten timers or callbacks, detached DOM elements still referenced in JavaScript, and not releasing large data structures.
To detect memory leaks, you can use tools like Chrome DevTools’ Memory tab which provides ways to profile and snapshot memory usage, allowing developers to identify and isolate objects that aren’t being garbage collected.
Preventing memory leaks involves:
- Regularly reviewing and profiling your code’s memory consumption.
- Ensuring event listeners are removed when they’re no longer needed.
- Avoiding global variables that persist.
- Using weak data structures like
WeakMap
andWeakSet
when applicable.
Chrome DevTools official documentation on Memory
Debouncing and Throttling
Question: Explain debouncing and throttling in the context of event handlers. Provide implementation examples.
Expected Answer
Debouncing and throttling are techniques to control the rate at which a function is executed.
Debouncing: Ensures that a function does not get called again until a certain amount of time has passed since it was last called. This is especially useful for event handlers that respond to user input, like window resizing or key presses.
Throttling: Guarantees that the function will be called at most once in a specified time period.
Example implementations:
Debounce:
function debounce(func, delay) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), delay);
};
}
// Usage
window.addEventListener('resize', debounce(handleResize, 300));
Throttle:
1function throttle(func, limit) {
2 let inThrottle;
3 return function(...args) {
4 if (!inThrottle) {
5 func.apply(this, args);
6 inThrottle = true;
7 setTimeout(() => inThrottle = false, limit);
8 }
9 };
10}
11
12// Usage
13window.addEventListener('scroll', throttle(handleScroll, 100));
Modules and Module Loaders
Question: How do ES6 modules work and how do they differ from CommonJS or AMD?
Expected Answer
ES6 modules introduce a standardized module format for JavaScript. The main keywords are import
to bring in functions or objects from another module and export
to make them available to other modules.
Differences:
- Syntax: ES6 uses
import
/export
while CommonJS usesrequire
/module.exports
and AMD usesdefine
andrequire
. - Loading: ES6 modules are statically analyzable which means you can determine imports and exports at compile time (statically) as opposed to runtime.
- Scope: In ES6 each module is in strict mode by default and has its own top-level scope.
Module loaders/bundlers like Webpack or Rollup come into play to bundle these modules into a format suitable for the browser, and they also provide features like tree shaking (eliminating unused exports).
Official documentation on ES6 Modules
Conclusion
Understanding concepts such as hoisting, memory management, event optimization, and module systems are paramount for efficient and optimized JavaScript development. It’s essential not just for clearing interviews but for ensuring code quality in real-world applications. Dive deeper into each topic, create your own examples, and test them. This iterative practice will solidify your understanding.
Additional Resources
- MDN JavaScript Guide
- Webpack Concepts
- Lodash Documentation for cloneDeep
- Debouncing and Throttling Explained
- JavaScript Memory Profiling in Chrome DevTools
Remember, practice on platforms like codedamn will always keep your skills sharp! Happy coding!
Sharing is caring
Did you like what Vishnupriya wrote? Thank them for their work by sharing it on social media.
No comments so far
Curious about this topic? Continue your journey with these coding courses: