JavaScript Interview Questions - Must-Know These Tricky Questions
Summary
Our blog offers a deep dive into JavaScript interview preparation, beyond just tricky questions. It provides unique tips on engaging interviewers as well. Ideal for both newcomers and seasoned developers, this guide helps you navigate and conquer JavaScript interview challenges.
Table of Content
JavaScript interviews aren't just about knowing the basics; they're about thinking on your feet and tackling puzzles that push your limits.
In this blog, we'll explore some of the trickiest JavaScript questions you might encounter in interviews.
So, whether you're a seasoned developer or just starting out, join us as we navigate through the twists and turns of JavaScript's maze of tricky interview questions. Let's dive in and conquer them together!
Tricky JavaScript Interview Questions
Q1: Explain the output of the following code.
var person = {
name: 'John',
greet: function() {
console.log('Hello, ' + this.name);
}
};
var greetFunc = person.greet;
greetFunc();
Solution:
The output will be Hello, undefined
. When greetFunc()
is called, it's not called in the context of the person
object. Therefore, within the greet
function, this
refers to the global object (in non-strict mode) or undefined
(in strict mode). Since name
is not defined in the global scope, this.name
evaluates to undefined
.
Q2: Describe what happens when you run the following code.
console.log('Start');
setTimeout(function() {
console.log('Timeout');
}, 0);
Promise.resolve().then(function() {
console.log('Promise');
});
console.log('End');
Answer: The output will be:
Start
End
Promise
Timeout
This is because setTimeout
is placed in the callback queue with a delay of 0 milliseconds, but it's executed after the current synchronous execution context completes. Thus, "Timeout" is logged after "End". However, the Promise callback is executed asynchronously through the microtask queue, so "Promise" is logged before "Timeout".
Difference Between Java And Javascript
Q3: Explain how web workers enable concurrency in JavaScript and how they communicate with the main thread. Discuss the advantages and limitations of using web workers in web applications.
Web workers allow JavaScript code to run in background threads separate from the main UI thread, enabling concurrent execution of tasks. They communicate with the main thread through message passing, allowing data to be exchanged without shared memory. Web workers are beneficial for CPU-intensive tasks and long-running operations but have limitations such as restricted access to the DOM, the inability to directly manipulate the UI, and increased memory overhead due to message passing.
Q4: Describe how Abstract Syntax Trees (AST) are used in JavaScript tooling for code analysis and transformation. Provide an example demonstrating the transformation of code using AST manipulation.
ASTs represent the structure of code as a tree of nodes, with each node representing a syntactic element of the code (e.g., variable declarations, function calls, expressions). JavaScript tooling such as Babel and ESLint utilize ASTs for tasks like transpiling modern JavaScript syntax to older versions, static code analysis, and code transformation/refactoring.
For example, Babel can transform arrow functions to traditional function expressions using AST manipulation.
ES5, ES6, ES7, ES8, ES9: What’s new in each Version of JavaScript
Q5: Convert the following callback-based code into a more readable and maintainable Promise-based code.
getUser(function(user) {
getProfile(user, function(profile) {
getPosts(user, function(posts) {
console.log('User:', user);
console.log('Profile:', profile);
console.log('Posts:', posts);
});
});
});
Answer: The code converted to Promise-based would look like this:
getUser()
.then(user => getProfile(user))
.then(profile => getPosts(user))
.then(posts => {
console.log('User:', user);
console.log('Profile:', profile);
console.log('Posts:', posts);
})
.catch(error => console.error(error));
This creates a more readable and maintainable structure by chaining Promises and handling errors with .catch()
. Each then
block waits for the previous operation to complete before executing the next one, avoiding the nested callback structure known as "Callback Hell".
Q6: How can you handle errors in asynchronous operations in JavaScript, such as promises or async/await
?
When dealing with asynchronous code, errors can occur at a later time after the function has seemingly returned. Here are two ways to handle errors:
Promises: Use.catch()
method on a promise to handle any errors that might be thrown during the asynchronous operation.fetch("https://api.example.com/data")
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
Async/await: Use try...catch
blocks to wrap your asynchronous code using await
.
async function fetchData() {
try {
const response = await fetch("https://api.example.com/data");
const data = await response.json();
console.log(data);
} catch (error) {
console.error(error);
}
}
fetchData();
Top Javascript Interview Questions And Answers
Q7: Deep dive into the Proxy object in JavaScript: how it works, its use cases, and an example demonstrating its utility.
The Proxy object in JavaScript is a powerful feature introduced in ECMAScript 2015 (ES6) that allows developers to create a wrapper for a target object, intercepting and customizing operations like property access, assignment, enumeration, and function invocation.
How it works:
A Proxy object is created with two parameters: the target object and a handler object that defines "traps", methods that provide property access. This enables intercepting actions on the target object, offering opportunities to layer additional behavior over the basic operation (e.g., validation, logging, property watching).
Use cases:
Validation: Ensuring that property values meet certain criteria before setting them.
Logging and profiling: Intercepting operations to log actions or measure performance.
Data binding and observable objects: Automatically updating UI elements or triggering reactions to property changes.
Access control: Restricting access to certain properties based on specific conditions.
Example demonstrating utility:
const validator = {
set: function(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('Age is not an integer');
}
if (value <= 0) {
throw new RangeError('Age must be a positive integer');
}
}
// The default behavior to store the value
obj[prop] = value;
// Indicate success
return true;
}
};
const person = new Proxy({}, validator);
person.age = 25; // Works
person.age = 'old'; // Throws TypeError
person.age = -1; // Throws RangeError
Q8: Explain how tagged template literals can be used for HTML escaping and why this approach can be more efficient than traditional string concatenation or templating methods in JavaScript.
function htmlEscape(literals, ...substitutions) {
let result = "";
// Interweave the literals with the substitutions
for (let i = 0; i < substitutions.length; i++) {
result += literals[i];
result += substitutions[i]
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');
}
// Add the final literal
result += literals[literals.length - 1];
return result;
}
const userInput = '<script>alert("xss")</script>';
const output = htmlEscape`<div>${userInput}</div>`;
Answer: Tagged template literals allow for safer and potentially more optimized HTML content generation by enabling automatic escaping of injection points within the template literal. This method is more efficient than traditional string concatenation or templating methods because it avoids the overhead of repeatedly parsing the template string and manually escaping each substitution. Additionally, engines like V8 can optimize tagged template literals by caching the parsed template, thus reducing the parsing overhead for templates used in tight loops or frequently called functions.
Q9: Discuss how the temporal dead zone (TDZ) in JavaScript’s block-scoped declarations (let
and const
) can be exploited in a security-sensitive context to prevent unauthorized access to variables.
(function() {
if (typeof unauthorizedAccess === "function") {
throw new Error("Security breach detected!");
}
// Temporal Dead Zone for `secret`
try {
secret; // ReferenceError: Cannot access 'secret' before initialization
} catch (e) {
console.log("Access to 'secret' is restricted until declaration");
}
// `secret` is safely declared after the security checks
let secret = "42";
})();
Answer: The Temporal Dead Zone (TDZ) refers to the period between entering the block scope where a variable is declared (using let
or const
) and the actual declaration statement. Accessing the variable in this zone results in a ReferenceError
. This behavior can be leveraged in security-sensitive contexts to ensure that sensitive variables are not accessed before the security checks are performed, thus preventing unauthorized or premature access. By strategically placing security checks before the declaration of sensitive variables, the TDZ acts as an additional safeguard against tampering or unauthorized access attempts within the scope of a function or block, enhancing the security posture of the code.
Top 55 Java Interview Questions and Answers
Q10: Consider the following JavaScript code snippet
function mysteryFunction() {
console.log(x); // Step 1
var x = 20;
console.log(x); // Step 2
}
x = 10;
mysteryFunction();
console.log(x); // Step 3
Without running the code, answer the following questions:
-
What is printed at Step 1 and why?
-
What is printed at Step 2 and why?
-
What is printed at Step 3 and why?
-
Considering the scope of
x
insidemysteryFunction
, how does JavaScript's execution context affect the visibility ofx
outside the function, and what concept is at play here?
Answer:
-
Step 1:
undefined
is printed. Even thoughx
is assigned10
beforemysteryFunction
is called, thevar x = 20;
declaration inside the function hoists the declaration ofx
to the top ofmysteryFunction
. However, the assignment happens after theconsole.log(x);
, so at the point of logging,x
isundefined
within the function scope. -
Step 2:
20
is printed. InsidemysteryFunction
, after thevar x = 20;
line, the local variablex
(specific tomysteryFunction
's execution context) is assigned the value20
. This localx
shadows any otherx
variable in the outer scope for the duration of the function's execution. -
Step 3:
10
is printed. OutsidemysteryFunction
, the global variablex
(which was assigned10
before the function call) remains unchanged by the local operations withinmysteryFunction
. This demonstrates how local variable declarations (usingvar
) inside functions do not affect the value of global variables of the same name. -
The concept at play here is scoping and hoisting. JavaScript's function scope means that variables declared with
var
inside a function are local to that function, regardless of where they are declared within the function body. Thevar x = 20;
declaration (and allvar
declarations) are hoisted to the top of their containing scope, but their assignments are not. This results inx
beingundefined
when accessed before its assignment within the same scope. The globalx
remains accessible outsidemysteryFunction
, demonstrating how different execution contexts (global vs. function scope) maintain separate environments for variables, a fundamental aspect of JavaScript's execution context behavior.
Q11. Consider the following code.
-
What will be logged to the console first and why?
-
What will be the output of the
console.log(mystery);
statement and why? -
Explain the role of the
then
function in the context of theawait
operator.
function MysteryAsyncFunction() {}
MysteryAsyncFunction.prototype.then = function(onFulfilled) {
setTimeout(() => onFulfilled("done"), 1000);
};
(async () => {
const mystery = await new MysteryAsyncFunction();
console.log(mystery);
})();
console.log(typeof MysteryAsyncFunction().then);
Answer:
-
The first output will be
"function"
because theconsole.log(typeof MysteryAsyncFunction().then);
statement executes synchronously and logs the type of thethen
method defined onMysteryAsyncFunction
's prototype, which is a function. This line of code executes before any asynchronous operations. -
The output of the
console.log(mystery);
statement will be"done"
. This is because theawait
operator can handle any thenable object, which is an object with athen
method. Here,MysteryAsyncFunction
creates a thenable due to its prototype having athen
method. Theawait
expression waits for thethen
function to call itsonFulfilled
callback, which it does via asetTimeout
with a delay of 1000 milliseconds. Once thesetTimeout
callback executes, it resolves the awaited promise with the value"done"
, which is then logged to the console. -
The
then
function plays a critical role when used with theawait
operator becauseawait
can operate on any object that implements a.then()
method (i.e., thenables), not just instances ofPromise
. In this case, thethen
function serves as a custom implementation that allows an instance ofMysteryAsyncFunction
to be treated as a promise-like object. Theawait
operator waits for thethen
function to invoke its callback (mimicking promise resolution), and the value passed to this callback is treated as the resolved value of the awaited expression.
Q12: What will be the output of console.log(buddy.getName());
and why?
function Animal(name) {
this.name = name;
}
Animal.prototype.getName = function() {
return this.name;
};
function Dog(name) {
Animal.call(this, name);
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.getName = function() {
return "Dog: " + Animal.prototype.getName.call(this);
};
const buddy = new Dog("Buddy");
delete buddy.name;
console.log(buddy.getName());
Answer:
The output will be "Dog: undefined"
. When the name
property is deleted from the buddy
instance, the getName
method falls back to the Animal.prototype.getName
method because Dog.prototype.getName
calls it explicitly. Since this.name
is undefined
at this point, the concatenated result is "Dog: undefined"
Q13: What is the exact order of the console log messages and why?
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
Answer:
The order will be:
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
This order is determined by the JavaScript event loop and microtask queue processing. Promises and async/await are microtasks which have higher priority and run before the next event loop iteration, while setTimeout
callbacks are tasks that run in subsequent iterations.
Q14: What will be the output of both console.log
statements and why?
function Obj() {}
Obj.prototype.x = 10;
const obj1 = new Obj();
const obj2 = new Obj();
Obj.prototype = { x: 20 };
const obj3 = new Obj();
console.log(obj1.x, obj2.x, obj3.x);
Obj.prototype.x = 30;
console.log(obj1.x, obj2.x, obj3.x);
Answer:
The output will be:
10 10 20
10 10 30
The first set of logs reflects the state before and after the prototype of Obj
was reassigned. obj1
and obj2
were created with the original prototype, so they log 10
. obj3
, created after the prototype reassignment, logs 20
. The second set of logs shows that modifying the property on the new prototype affects only instances that inherit from it (obj3
), while obj1
and obj2
retain their values because they are linked to the original prototype.
Q15:
-
Will the
deepClone
function work correctly for objects with circular references, such ascircularObj
? Why or why not? -
How does the
deepClone
function handle circular references? -
What will be the outcome of cloning
circularObj
? Describe any potential issues or limitations with this approach.
function deepClone(obj, hash = new WeakMap()) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (hash.has(obj)) {
return hash.get(obj);
}
let clone = Array.isArray(obj) ? [] : {};
hash.set(obj, clone);
Object.keys(obj).forEach(
key => (clone[key] = deepClone(obj[key], hash))
);
return clone;
}
// Example object with circular reference
const circularObj = {
name: "circle",
selfRef: null, // This will be set to reference circularObj itself
};
circularObj.selfRef = circularObj;
// Attempt to clone circularObj
const clonedObj = deepClone(circularObj);
Answer:
-
Yes, the
deepClone
function provided will work correctly for objects with circular references. It uses aWeakMap
to keep track of objects that have already been visited and cloned. When it encounters a circular reference, it returns the previously cloned object instead of trying to clone it again, thus preventing an infinite loop. -
The
deepClone
function handles circular references using thehash
parameter, aWeakMap
. When it first encounters an object, it stores the object and its clone in theWeakMap
. If the same object is encountered again (indicating a circular reference), the function retrieves and returns the cloned object from theWeakMap
instead of attempting to clone it again. This mechanism effectively breaks the circular reference in the cloned object. -
Cloning
circularObj
with thedeepClone
function will produce a deep clone of the original object, including handling the circular reference correctly. The cloned object (clonedObj
) will have aselfRef
property that correctly points to itself (clonedObj
), mirroring the structure of the originalcircularObj
without causing infinite loops or errors. The potential issue with this approach is the use ofWeakMap
, which cannot be serialized directly. This means that while the function handles circular references effectively in memory, serializing and deserializing such objects (e.g., for network transmission or storage) would require additional handling to preserve the circular structures.
Unique tips while facing a JavaScript interview
Preparing for a JavaScript interview requires a blend of understanding fundamental concepts, practical problem-solving skills, and staying updated with the latest features and best practices.
Here are some unique tips to help you stand out:
-
Interviewer Engagement - Engage your interviewer by asking them to provide code snippets or scenarios for you to optimize or refactor. This interactive approach can make you stand out by demonstrating confidence and real-time problem-solving skills.
Algorithmic Challenges Using Functional Programming - Solve algorithmic challenges using JavaScript's functional programming features, such as map, reduce, filter, and compose. This can demonstrate both your algorithmic thinking and your understanding of functional programming concepts.
-
Explore JavaScript Engines - Gain a basic understanding of how JavaScript engines like V8 (Chrome, Node.js), SpiderMonkey (Firefox), and JavaScriptCore (Safari) work. Discussing JavaScript optimizations, JIT compilation, and garbage collection can showcase deep knowledge.
-
Understand the Specification - Dive into the ECMAScript Language Specification. Being able to reference the specification when discussing why JavaScript works the way it does can impress interviewers. For example, discuss how the spec defines the behavior of Array.prototype.reduce() or the intricacies of variable hoisting.
-
Master the 'this' Keyword in All Contexts - Prepare examples that demonstrate how this behaves in different contexts: in global scope, inside functions, in arrow functions, and when using methods like call, apply, and bind. Offering clear, concise explanations will stand out.
-
JavaScript Internals and Design Patterns - Discuss JavaScript internals like the Call Stack, Memory Heap, Event Queue, and Event Loop. Understanding design patterns (Singleton, Factory, Observer, etc.) and their use cases in JavaScript specifically can also highlight your deep understanding.
-
Security Considerations in JavaScript - Discuss common security vulnerabilities in JavaScript applications (e.g., Cross-Site Scripting (XSS), Cross-Site Request Forgery (CSRF), and how to prevent them. Understanding security is often overlooked in interviews but is highly valuable.
Frequently Asked Questions
How hard is JavaScript for beginners?
JavaScript can be both simple and challenging for beginners. Its syntax is straightforward, but concepts like asynchronous programming and DOM manipulation can be tricky.
JavaScript is a huge thing to learn. What is the quickest way to learn JavaScript as per the industry needs?
The quickest and smarter way to learn all the latest industry-required skills is to join a web developer course.
Can a non-tech learn JavaScript?
Absolutely, a non-tech person can learn JavaScript! In fact, JavaScript is often recommended as one of the first programming languages for beginners due to its versatility and the immediate feedback loop it offers through web development.
Can you make a career in JS?
Yes, you can definitely make a career in JavaScript, just like Jitendra Kumar whose remarkable journey from a humble village in Jharkhand to achieving great success as a Java developer at PeopleTech company and a significant salary increase is nothing short of awe-inspiring.
In fact, JavaScript is one of the most in-demand programming languages in the world of web development, and its popularity continues to grow.
What are the different career paths in the web development world?
Here are several career paths and opportunities that involve JavaScript- Web Development Roadmap (2024): How to Become a Web Developer