Introduction
Welcome to the JavaScript Mastery: Comprehensive MCQ Challenge! This chapter is meticulously designed to test and solidify your understanding of JavaScript’s most intricate and often counter-intuitive behaviors. Far beyond basic syntax, this challenge delves into the “weird parts” of JavaScript that often trip up even experienced developers.
Whether you’re aiming for a mid-level frontend role or an architect position, mastering these concepts—including coercion, hoisting, scope, closures, prototypes, this binding, the event loop, asynchronous patterns, and memory management—is crucial. Interviewers at top companies frequently use these topics to gauge a candidate’s deep understanding of the language’s internals and their ability to debug complex, real-world scenarios.
This comprehensive Multiple Choice Question (MCQ) section is tailored to prepare you for the trickiest JavaScript questions you might encounter. Each question is crafted to highlight a specific nuance or edge case, providing detailed explanations to ensure you not only know the answer but also understand the underlying “why” in accordance with modern JavaScript standards (ECMAScript 2025/2026).
MCQ Section: JavaScript’s Weird Parts
Test your knowledge with these challenging questions covering advanced JavaScript concepts.
Question 1: Hoisting and Function Declarations
What will be the output of the following JavaScript code?
var x = 1;
function foo() {
x = 10;
return;
function x() {}
}
foo();
console.log(x);
A. 1
B. 10
C. undefined
D. ReferenceError
Correct Answer: A
Explanation: This question tests your understanding of hoisting and function-scoped variable declarations.
- Global Scope:
var x = 1;declares a global variablexand initializes it to1. fooFunction Scope: Insidefoo, there’s a function declarationfunction x() {}. Due to hoisting, this function declaration is hoisted to the top offoo’s scope. This creates a local variablex(which is a function) withinfoo’s scope, effectively shadowing the globalx.- Assignment within
foo: The linex = 10;then attempts to assign10tox. Since the localx(the hoisted function) exists, this assignment modifies the local functionxto become the number10. returnstatement: Thereturnstatement immediately exits thefoofunction.console.log(x): Afterfoo()executes, theconsole.log(x)statement accesses the globalx. Since the assignmentx = 10modified the localxinsidefooand not the globalx, the globalxremains1.
Key Points:
- Function declarations are hoisted before variable declarations within their respective scopes.
- A hoisted function declaration creates a local variable that can shadow a global one.
- Assignments inside a function will affect the local variable if one exists, not the global one, unless explicitly referenced (e.g.,
window.x).
Common Mistakes:
- Assuming
x = 10modifies the globalx. - Forgetting that function declarations are hoisted.
- Not understanding how local scope shadows global scope.
Follow-up: What if var x = 1; was let x = 1; and function x() {} was removed, but let x = 10; was added inside foo? How would console.log(x) behave?
Question 2: Type Coercion and Equality
What is the result of [] == ![] in JavaScript?
A. true
B. false
C. TypeError
D. NaN
Correct Answer: A
Explanation:
This is a classic example of JavaScript’s abstract equality (==) operator and type coercion rules.
- Right-hand side (
![]):- The
!(logical NOT) operator first tries to convert its operand to a boolean. - An empty array
[]is a “truthy” value in JavaScript. - So,
![]evaluates to!true, which isfalse.
- The
- Left-hand side (
[]):- Now we have
[] == false. - According to the ECMAScript specification for
==when comparing an object (like[]) with a boolean (false):- The boolean
falseis first converted to a number:falsebecomes0. - So, the comparison becomes
[] == 0. - Next, the object
[]is converted to a primitive value. For arrays, this involves calling[].valueOf()(which returns[]) and then[].toString()(which returns an empty string""). - So, the comparison becomes
"" == 0. - Finally, the empty string
""is converted to a number:""becomes0. - So, the comparison becomes
0 == 0.
- The boolean
0 == 0istrue.
- Now we have
Key Points:
![]evaluates tofalsebecause[]is truthy.- When comparing an object with a boolean using
==, the boolean is converted to a number (0or1). - The object is then converted to a primitive (usually via
toString()) and then potentially to a number for comparison.
Common Mistakes:
- Assuming
==behaves like===(strict equality). - Incorrectly converting
[]tofalseor0prematurely. - Not knowing the order of operations for type coercion.
Follow-up: Explain the difference between == and ===. Provide an example where 0 == null is false but 0 == undefined is also false, yet null == undefined is true.
Question 3: Closures and Loop Variables
Consider the following code:
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 100);
}
What will be logged to the console?
A. 0, 1, 2 (after 100ms each)
B. 3, 3, 3 (after 100ms)
C. 0, 1, 2 (immediately)
D. undefined, undefined, undefined
Correct Answer: B
Explanation:
This is a classic closure problem related to var in loops and asynchronous operations.
varand Function Scope: Thevar ideclaration is function-scoped (or global-scoped if outside a function). In aforloop,vardoes not create a newifor each iteration. Instead, there’s only oneivariable shared across all iterations.- Asynchronous
setTimeout: ThesetTimeoutfunction schedules its callback to run after the current execution stack has cleared and a minimum delay has passed. The loop completes its execution almost instantly. - Loop Completion: By the time the
setTimeoutcallbacks actually execute (after approximately 100ms), theforloop has already finished. The value ofihas incremented to3(becausei < 3is no longer true, soi++made it3). - Closure: Each
setTimeoutcallback forms a closure over the outer scope whereiis defined. When the callbacks finally execute, they all refer to the same, singleivariable, which now holds the value3.
Key Points:
varis function-scoped, not block-scoped.setTimeoutcallbacks are executed asynchronously, after the main thread finishes.- Closures capture variables by reference, not by value, from their outer lexical environment.
Common Mistakes:
- Assuming
iis block-scoped likeletorconst. - Not understanding the asynchronous nature of
setTimeout.
Follow-up: How would you fix this code to log 0, 1, 2? Provide at least two different modern JavaScript solutions.
Question 4: this Binding in Arrow Functions
What will be logged to the console when obj.greet() is called?
const obj = {
name: 'Alice',
greet: function() {
setTimeout(() => {
console.log(`Hello, my name is ${this.name}`);
}, 0);
}
};
obj.greet();
A. Hello, my name is Alice
B. Hello, my name is undefined
C. Hello, my name is (empty string)
D. TypeError: Cannot read properties of undefined (reading 'name')
Correct Answer: A
Explanation:
This question highlights a key characteristic of arrow functions regarding this binding.
obj.greet()call: Whenobj.greet()is called, thegreetmethod is invoked as a method ofobj. Therefore, inside thegreetfunction,thisrefers toobj.setTimeoutcallback (Arrow Function): The callback passed tosetTimeoutis an arrow function (() => { ... }). Arrow functions do not have their ownthisbinding. Instead, they lexically inheritthisfrom their enclosing scope.- Lexical
this: In this case, the arrow function’s enclosing scope is thegreetmethod. Sincethisinsidegreetisobj, the arrow function’sthiswill also beobj. - Accessing
name: Thus,this.nameinside the arrow function correctly refers toobj.name, which is'Alice'.
Key Points:
- Regular functions (
function() {}) define their ownthisbased on how they are called. - Arrow functions (
() => {}) do not define their ownthis; they inheritthisfrom their immediate lexical (enclosing) scope. setTimeoutitself does not change thethiscontext for the callback; thethisbehavior depends solely on whether the callback is a regular function or an arrow function.
Common Mistakes:
- Assuming
setTimeoutalways bindsthisto the global object (windowin browsers,undefinedin strict mode/modules). This is true for regular functions passed tosetTimeoutwithout explicit binding, but not for arrow functions. - Confusing
thisbinding rules for regular functions with arrow functions.
Follow-up: How would the output change if the setTimeout callback was a regular function (function() { ... }) instead of an arrow function? How could you then ensure this.name still refers to 'Alice'?
Question 5: Event Loop and Microtasks vs. Macrotasks
What is the order of outputs for the following code?
console.log('Start');
setTimeout(() => {
console.log('Timeout 1');
}, 0);
Promise.resolve().then(() => {
console.log('Promise 1');
});
setTimeout(() => {
console.log('Timeout 2');
}, 0);
Promise.resolve().then(() => {
console.log('Promise 2');
});
console.log('End');
A. Start, End, Promise 1, Promise 2, Timeout 1, Timeout 2
B. Start, Promise 1, Promise 2, End, Timeout 1, Timeout 2
C. Start, End, Timeout 1, Promise 1, Timeout 2, Promise 2
D. Start, Promise 1, Timeout 1, End, Promise 2, Timeout 2
Correct Answer: A
Explanation: This question tests your understanding of the JavaScript event loop, specifically the distinction between macrotasks and microtasks.
Main Thread/Call Stack:
console.log('Start')executes immediately.setTimeout(() => { console.log('Timeout 1'); }, 0)schedules a macrotask.Promise.resolve().then(() => { console.log('Promise 1'); })schedules a microtask.setTimeout(() => { console.log('Timeout 2'); }, 0)schedules another macrotask.Promise.resolve().then(() => { console.log('Promise 2'); })schedules another microtask.console.log('End')executes immediately.- The call stack is now empty.
Event Loop Cycle:
- The event loop first checks the microtask queue. It processes all microtasks before moving to the next macrotask.
Promise 1is logged.Promise 2is logged.
- After the microtask queue is empty, the event loop takes the next macrotask from the macrotask queue.
Timeout 1is logged.
- The event loop checks for microtasks (none). Then it takes the next macrotask.
Timeout 2is logged.
- The event loop first checks the microtask queue. It processes all microtasks before moving to the next macrotask.
Key Points:
- Macrotasks:
setTimeout,setInterval,setImmediate, I/O, UI rendering. - Microtasks:
Promise.then(),queueMicrotask(),MutationObserver. - The event loop prioritizes microtasks. All microtasks in the queue are processed after the current macrotask completes and before the next macrotask is picked up.
Common Mistakes:
- Assuming
setTimeout(..., 0)means it runs immediately after the current script. - Not understanding that microtasks have higher priority than macrotasks.
- Incorrectly ordering multiple promises or multiple timeouts.
Follow-up: If queueMicrotask(() => console.log('Microtask')) was added before the first setTimeout, where would its output appear?
Question 6: Prototype Chain and Property Access
What will be the output of the following code?
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
return `${this.name} makes a sound.`;
};
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.speak = function() {
return `${this.name} barks.`;
};
const myDog = new Dog('Buddy', 'Golden Retriever');
const genericAnimal = new Animal('Leo');
console.log(myDog.speak());
console.log(genericAnimal.speak());
A. Buddy barks., Leo makes a sound.
B. Buddy makes a sound., Leo makes a sound.
C. Buddy barks., Leo barks.
D. TypeError: myDog.speak is not a function, Leo makes a sound.
Correct Answer: A
Explanation: This question tests your understanding of classical inheritance patterns in JavaScript using prototypes.
AnimalConstructor and Prototype:Animalis a constructor, and itsspeakmethod is onAnimal.prototype.DogConstructor:DogcallsAnimal.call(this, name)to inherit properties fromAnimal.DogPrototype Chain Setup:Dog.prototype = Object.create(Animal.prototype): This line correctly sets up the prototype chain.Dog.prototypenow hasAnimal.prototypeas its__proto__. This means if a property isn’t found onDog.prototype, JavaScript will look for it onAnimal.prototype.Dog.prototype.constructor = Dog: This correctly resets theconstructorproperty, which is good practice.
Dog.prototype.speakOverride:Dog.prototype.speak = function() { ... }overrides thespeakmethod that would otherwise be inherited fromAnimal.prototype. WhenmyDog.speak()is called, JavaScript finds thespeakmethod directly onDog.prototypefirst.myDog.speak():myDogis an instance ofDog. WhenmyDog.speak()is called, the JavaScript engine looks forspeakonmyDog’s own properties, then onDog.prototype. It finds the overriddenspeakmethod onDog.prototype, which logsBuddy barks..genericAnimal.speak():genericAnimalis an instance ofAnimal. WhengenericAnimal.speak()is called, the engine looks forspeakongenericAnimal’s own properties, then onAnimal.prototype. It findsAnimal.prototype.speak, which logsLeo makes a sound..
Key Points:
Object.create()is the standard way to set up prototypal inheritance in ES5+.- Methods defined directly on a child’s prototype (
Dog.prototype.speak) take precedence over methods higher up the prototype chain (Animal.prototype.speak). thisinside a prototype method refers to the instance on which the method was called.
Common Mistakes:
- Using
Dog.prototype = new Animal();which incorrectly creates an instance of Animal and can lead to issues with shared properties. - Forgetting to reset
Dog.prototype.constructor. - Incorrectly predicting which
speakmethod will be called.
Follow-up: How would you modify the Dog.prototype.speak method to call the Animal.prototype.speak method and then add its own specific dog sound?
Question 7: Scoping with var, let, and const
What will be the output of the following code snippet?
(function() {
var a = 1;
let b = 2;
const c = 3;
if (true) {
var a = 4;
let b = 5;
const c = 6;
console.log(a);
console.log(b);
console.log(c);
}
console.log(a);
console.log(b);
console.log(c);
})();
A. 4, 5, 6, 4, 2, 3
B. 4, 5, 6, 1, 2, 3
C. 4, 5, 6, 4, 5, 6
D. 4, 5, 6, 1, 5, 6
Correct Answer: A
Explanation:
This question tests your understanding of var (function-scoped) vs. let and const (block-scoped).
Outer Scope (IIFE):
var a = 1;let b = 2;const c = 3;These create variables in the immediate function scope of the IIFE.
Inner
ifBlock Scope:var a = 4;: This re-declares the sameavariable from the outer function scope, becausevaris function-scoped. It effectively reassigns the value ofato4.let b = 5;: This declares a new, block-scoped variablebspecific to theifblock. It shadows the outerb.const c = 6;: This declares a new, block-scoped variablecspecific to theifblock. It shadows the outerc.
Inside
ifblockconsole.logs:console.log(a);outputs4(the reassigned function-scopeda).console.log(b);outputs5(the block-scopedb).console.log(c);outputs6(the block-scopedc).
Outside
ifblockconsole.logs:console.log(a);outputs4(the function-scopedawas reassigned to4inside theifblock, and this change persists).console.log(b);outputs2(the block-scopedb=5is out of scope, so the outerb=2is accessed).console.log(c);outputs3(the block-scopedc=6is out of scope, so the outerc=3is accessed).
Key Points:
vardeclarations are hoisted and function-scoped. Re-declaring avarvariable within the same function scope (even in nested blocks) effectively reassigns the existing variable.letandconstdeclarations are block-scoped. Declaringletorconstvariables within a nested block creates new variables that only exist within that block, shadowing any variables of the same name in outer scopes.
Common Mistakes:
- Treating
varas block-scoped. - Assuming
letandconstreassign outer variables instead of creating new ones.
Follow-up: What would happen if the second var a = 4; was changed to let a = 4;?
Question 8: Memory Management and Closures
Consider the following code snippet:
function createCounter() {
let count = 0;
return function() {
count++;
console.log(count);
};
}
const counter1 = createCounter();
const counter2 = createCounter();
counter1();
counter1();
counter2();
What will be the output, and how does memory management relate to count in this scenario?
A. 1, 2, 1. count is garbage collected after createCounter finishes each time.
B. 1, 2, 1. Each counter function maintains its own independent count variable due to closures.
C. 1, 2, 3. count is a shared global variable.
D. 1, 1, 1. count is reset on each call to the inner function.
Correct Answer: B
Explanation: This question demonstrates a fundamental use case of closures and their implications for memory.
createCounterExecution: WhencreateCounter()is called, a new execution context is created. Inside this context, a newlet count = 0;variable is initialized.- Returning Inner Function:
createCounterthen returns an inner function. This inner function “closes over” (forms a closure with) the lexical environment in which it was created. This environment includes thecountvariable. counter1andcounter2:const counter1 = createCounter();creates the first closure. Thiscounter1function now has its own privatecountvariable (initialized to 0) that it can access and modify.const counter2 = createCounter();creates a second, independent closure. Thiscounter2function has its own separatecountvariable (also initialized to 0) that it can access and modify.
- Execution:
counter1():count(forcounter1) becomes 1. Logs1.counter1():count(forcounter1) becomes 2. Logs2.counter2():count(forcounter2) becomes 1. Logs1.
Memory Management:
- The
countvariable forcounter1(and similarly forcounter2) is not garbage collected whencreateCounterfinishes because it is still being referenced by the returned inner function (counter1itself). As long ascounter1(orcounter2) exists and is accessible, its associatedcountvariable will remain in memory. - Each
countvariable is distinct and managed independently for each closure created.
Key Points:
- A closure allows an inner function to access variables from its outer (enclosing) function’s scope, even after the outer function has finished executing.
- Each time the outer function is called, a new set of closed-over variables is created for the returned inner function, making them private to that specific closure instance.
- Variables held by closures are not garbage collected as long as the closure itself is reachable.
Common Mistakes:
- Believing that
countis shared betweencounter1andcounter2. - Assuming
countis garbage collected aftercreateCountercompletes.
Follow-up: Describe a scenario where improper use of closures could lead to memory leaks in a long-running application.
Question 9: Tricky Coercion with + Operator
What is the result of 1 + "2" + 3 in JavaScript?
A. "123"
B. "33"
C. 6
D. NaN
Correct Answer: A
Explanation:
This question tests the behavior of the + operator, which can perform both addition and string concatenation based on its operands.
1 + "2":- The
+operator encounters a number (1) and a string ("2"). - When one operand is a string, the
+operator performs string concatenation. - The number
1is coerced into a string"1". - Result:
"1" + "2"becomes"12".
- The
"12" + 3:- Now the
+operator encounters a string ("12") and a number (3). - Again, due to the presence of a string, string concatenation occurs.
- The number
3is coerced into a string"3". - Result:
"12" + "3"becomes"123".
- Now the
Key Points:
- The
+operator has a dual role: arithmetic addition and string concatenation. - If any operand of
+is a string, the entire operation (from left to right) tends towards string concatenation. - Operations are evaluated from left to right.
Common Mistakes:
- Assuming all numbers will be added first, then concatenated.
- Incorrectly predicting the order of coercion.
Follow-up: What would be the output of 1 + + "2" + 3? Explain why.
Question 10: this Binding and call/apply/bind
What will be logged to the console?
const person = {
name: 'Charlie',
greet: function(city) {
console.log(`Hello, I'm ${this.name} from ${city}.`);
}
};
const anotherPerson = {
name: 'Diana'
};
person.greet.call(anotherPerson, 'New York');
A. Hello, I'm Charlie from New York.
B. Hello, I'm Diana from New York.
C. Hello, I'm undefined from New York.
D. TypeError: Cannot read properties of undefined (reading 'name')
Correct Answer: B
Explanation:
This question tests your understanding of explicit this binding using the call method.
person.greetmethod:greetis a regular function, so itsthiscontext is determined by how it’s called.call()method: Thecall()method is used to invoke a function with a specifiedthisvalue and arguments provided individually.person.greet.call(anotherPerson, 'New York')means:- Execute the
greetfunction. - Set
thisinsidegreettoanotherPerson. - Pass
'New York'as the first argument togreet(which maps tocity).
- Execute the
- Output: Inside
greet,this.namewill now refer toanotherPerson.name, which is'Diana'. Thecityparameter will be'New York'.
Key Points:
call(),apply(), andbind()are methods available on all functions in JavaScript.- They allow you to explicitly set the
thiscontext for a function call. call()takes arguments individually,apply()takes arguments as an array, andbind()returns a new function withthispermanently bound.
Common Mistakes:
- Forgetting that
call/apply/bindoverride the defaultthisbinding rules. - Confusing
callwithapplyorbind.
Follow-up: How would the syntax change if you wanted to use apply instead of call for the same outcome? What if you wanted to create a new function dianaGreet that always greets as Diana from New York, without immediately invoking it?
Practical Tips for Mastering Tricky JavaScript
- Deep Dive into ECMAScript Specification: For true mastery, occasionally refer to the official ECMAScript Language Specification (ES2025/2026). Understanding the spec’s abstract operations (e.g.,
ToPrimitive,ToBoolean,[[Call]]) demystifies coercion,thisbinding, and execution flow. - Practice, Practice, Practice: The best way to understand these concepts is by writing code and experimenting. Use online sandboxes (e.g., JSFiddle, CodePen, browser console) to test your hypotheses.
- Trace Execution Manually: For complex code puzzles, meticulously trace the execution line by line, keeping track of variable scopes,
thiscontext, and the event queue. Use a debugger to step through code. - Understand the “Why”: Don’t just memorize answers. For each tricky question, understand why JavaScript behaves that way. Is it due to hoisting rules? Coercion algorithms? The event loop’s microtask/macrotask priority?
- Focus on Core Concepts: While frameworks are important, these “weird parts” are fundamental to JavaScript itself. A strong grasp here will make you a better debugger and architect regardless of the libraries you use.
- Read Authoritative Articles/Books: Supplement your learning with well-regarded articles and books that specifically cover advanced JavaScript topics, such as “You Don’t Know JS” series by Kyle Simpson, or articles from MDN Web Docs.
- Whiteboard Practice: Explain these concepts to yourself or a peer on a whiteboard. Articulating the rules and drawing diagrams (e.g., for event loop or prototype chains) solidifies understanding.
Summary
This MCQ challenge has pushed you to think critically about JavaScript’s nuances, from hoisting and coercion to the intricacies of the event loop and this binding. Mastering these concepts is a hallmark of a proficient JavaScript developer and is essential for architecting robust, performant, and bug-free applications.
Continue to explore these areas, challenge your assumptions, and always strive to understand the underlying mechanisms. Your ability to navigate these “weird parts” confidently will distinguish you in any technical interview and empower you to write superior JavaScript code.
References
- MDN Web Docs (Mozilla Developer Network): The most comprehensive and authoritative resource for JavaScript documentation. Essential for understanding core concepts and APIs. (https://developer.mozilla.org/en-US/docs/Web/JavaScript)
- ECMAScript Language Specification: For the deepest understanding of how JavaScript works under the hood, refer to the official standard. (Search for “ECMAScript 2025 Specification” or “ECMAScript Language Specification” for the latest version, usually found on tc39.es or ecma-international.org)
- “You Don’t Know JS Yet” by Kyle Simpson: A highly recommended book series that dives deep into JavaScript’s core mechanisms, including scope, closures,
this, and prototypes. (Available on GitHub or various book retailers) - JavaScript Visualizer Tools (e.g., Loupe by Philip Roberts): Interactive tools that visualize the call stack, event queue, and microtask queue can be invaluable for understanding the event loop. (Search for “JavaScript event loop visualizer”)
- GeeksforGeeks - JavaScript Interview Questions: Offers a wide range of interview questions, including advanced and tricky ones, often with detailed explanations. (https://www.geeksforgeeks.org/javascript-interview-questions-and-answers/)
- Medium - Advanced JavaScript Interview Questions: Many skilled developers share insights and tricky questions on Medium. Searching for “Advanced JavaScript Interview Questions 2026” often yields good results. (e.g., articles similar to the search results provided)
This interview preparation guide is AI-assisted and reviewed. It references official documentation and recognized interview preparation resources.