Predict the Output: 10 JavaScript Prototypes, 'this' Binding, and Coercion Questions Every Developer Should Practise
In front-end technical screenings and pair-programming rounds, output prediction questions are among the most common formats for evaluating a candidate's core JavaScript proficiency. Rather than asking for textbook definitions, interviewers present short code snippets to evaluate whether an engineer understands runtime execution contexts, prototype delegation, and type coercion algorithms.
While modern frameworks abstract away raw DOM and state manipulation, production bugs often trace back to foundational mechanics: losing this in callback handlers, mutating shared prototype references, or misunderstanding abstract equality (==) and logical operator short-circuiting.
This guide walks through 10 high-frequency output prediction problems covering this binding, prototypal inheritance, and type coercion for developers with 0–5 years of experience.
Question Index
| # | Snippet Scenario | Focus Concept | Difficulty |
|---|---|---|---|
| 1 | Method Invocation vs Detached Function Call | Implicit vs lost this binding | Junior-Mid |
| 2 | Explicit and Hard Binding with bind() | Immutability of bound context | Mid |
| 3 | The new Operator with Explicit Returns | Object vs primitive return override | Mid |
| 4 | Prototypal Property Shadowing with Object.create | Own property creation vs shared mutation | Mid |
| 5 | Class Inheritance and Field Initializer Order | super() execution timing, field overrides | Mid-Senior |
| 6 | Abstract Equality (==) and ToPrimitive | valueOf vs toString coercion priority | Mid |
| 7 | Binary Addition (+) vs Unary Plus Coercion | String concatenation vs numeric conversion | Junior-Mid |
| 8 | Short-Circuit Evaluation & Nullish Coalescing | Falsy values (0, "") in ` | |
| 9 | Temporal Dead Zone (TDZ) in Parameter Scopes | Parameter scope evaluation, shadowing | Mid-Senior |
| 10 | Prototype Chain Enumeration vs Object.hasOwn | in operator vs own property checks | Mid |
Q1. Method Invocation vs Detached Function Call
Difficulty: Junior-Mid
Target Concept: Implicit binding, lost context in detached references, and arrow function lexical scoping.
Code Snippet
const user = {
name: 'Alice',
regularGreet() {
console.log('Regular:', this.name);
},
arrowGreet: () => {
console.log('Arrow:', typeof this !== 'undefined' ? this.name : undefined);
},
};
user.regularGreet();
const detached = user.regularGreet;
detached();
user.arrowGreet();
What is the exact console output in a standard non-strict browser environment?
Predicted Output
Regular: Alice
Regular: undefined
Arrow: undefined
Step-by-Step Execution Trace
user.regularGreet(): The function is called as an object method withuserpreceding the dot. Implicit binding setsthistouser. Logs:Regular: Alice.detached = user.regularGreet; detached(): The function reference is assigned to a standalone variable. Invokingdetached()without a receiver defaultsthisto the global object (window). Sincewindow.nameis undefined,this.nameevaluates toundefined. Logs:Regular: undefined.user.arrowGreet(): Arrow functions do not bind their ownthis. They capturethislexically from their enclosing scope at declaration time. Becauseuseris defined in the global scope (an object literal does not create a local execution context),arrowGreetinherits globalthis. Logs:Arrow: undefined.
Spoken Explanation & Common Mistakes
- Spoken Summary: "Regular functions determine
thisdynamically at call time based on the call-site. Callinguser.regularGreet()bindsthistouser, but detaching the reference to a variable executes it standalone, resettingthisto the global object. Arrow functions ignore call-site binding and inheritthislexically from their enclosing scope." - Common Mistake: Assuming arrow functions declared inside object literals bind
thisto the object. Object literals do not create an execution context."
Q2. Explicit and Hard Binding with bind()
Difficulty: Mid
Target Concept: Function.prototype.bind internal slot immutability and chained binding.
Code Snippet
function display() {
console.log(this.brand);
}
const laptop = { brand: 'Apple' };
const tablet = { brand: 'Google' };
const phone = { brand: 'Samsung' };
const boundLaptop = display.bind(laptop);
boundLaptop();
const reBound = boundLaptop.bind(tablet);
reBound();
boundLaptop.call(phone);
What is the exact console output?
Predicted Output
Apple
Apple
Apple
Step-by-Step Execution Trace
const boundLaptop = display.bind(laptop): Callingbind()returns an exotic bound function wrapper whose internal[[BoundThis]]slot is permanently set tolaptop.boundLaptop(): Executesdisplaywiththislocked tolaptop. Logs:Apple.const reBound = boundLaptop.bind(tablet): Callingbind()on an already-bound function creates an outer wrapper around the existing bound function. When invoked, the outer wrapper calls the inner bound function, which still executes withlaptop. Subsequentbindcalls cannot alter the initial[[BoundThis]]target. Logs:Apple.boundLaptop.call(phone): Bound functions overridecall()andapply()behavior. The bound function wrapper ignores thethisArgpassed tocall(phone). Logs:Apple.
Spoken Explanation & Common Mistakes
- Spoken Summary: "Once a function is bound with
.bind(), itsthistarget is permanently locked in the internal[[BoundThis]]slot. Subsequent calls to.bind(),.call(), or.apply()can pass new arguments, but cannot re-bind or override the originalthiscontext." - Common Mistake: Believing that chained
.bind()calls re-bind the function to the latest argument, or that explicit.call()overrides a previous.bind()."
Q3. The new Operator with Explicit Returns
Difficulty: Mid
Target Concept: Constructor return value mechanics, object vs primitive return overrides.
Code Snippet
function Vehicle(name) {
this.name = name;
return 'Automobile';
}
function Device(name) {
this.name = name;
return { customType: 'Hardware' };
}
const v = new Vehicle('Sedan');
const d = new Device('Phone');
console.log(v.name);
console.log(d.name);
console.log(d.customType);
What is the exact console output?
Predicted Output
Sedan
undefined
Hardware
Step-by-Step Execution Trace
new Vehicle('Sedan'):- A fresh object is created, inheriting from
Vehicle.prototype. this.name = 'Sedan'sets the property on the new object.- The constructor executes
return 'Automobile'. - Under the ECMAScript specification for
[[Construct]], returning a primitive (like a string, number, or boolean) is ignored. The operator returns the newly constructedthisinstance. v.namelogs:Sedan.
- A fresh object is created, inheriting from
new Device('Phone'):- A fresh object is created, and
this.name = 'Phone'executes. - The constructor executes
return { customType: 'Hardware' }. - Because the return value is an object (non-non-primitive), the engine discards the
thisinstance and returns the explicit object instead. dis{ customType: 'Hardware' }.d.nameisundefined, andd.customTypeisHardware.
- A fresh object is created, and
Spoken Explanation & Common Mistakes
- Spoken Summary: "When invoking a function with
new, returning a primitive value is completely ignored by the engine, returning the newly createdthisinstance. If the constructor returns an explicit object, that object overrides and replaces the instance." - Common Mistake: Assuming any return value overrides
new, overlooking the critical distinction between primitive and object returns."
Q4. Prototypal Property Shadowing with Object.create
Difficulty: Mid
Target Concept: Prototype delegation, property shadowing via assignment, and shared object references.
Code Snippet
const proto = {
count: 0,
tags: ['base'],
increment() {
this.count++;
}
};
const obj1 = Object.create(proto);
const obj2 = Object.create(proto);
obj1.increment();
obj1.tags.push('frontend');
console.log(obj1.count);
console.log(obj2.count);
console.log(proto.count);
console.log(obj1.tags.join(','));
console.log(obj2.tags.join(','));
What is the exact console output?
Predicted Output
1
0
0
base,frontend
base,frontend
Step-by-Step Execution Trace
Object.create(proto): Createsobj1andobj2as empty objects whose prototype links toproto.obj1.increment(): Executesproto.incrementwiththis === obj1.this.count++expands tothis.count = this.count + 1.- Reading
this.counttraverses the prototype chain and readsproto.count(0). - Adding
0 + 1assigns1tothis.count. In JavaScript, property assignment creates an own property on the receiver (obj1). obj1now ownscount: 1, shadowingproto.count.proto.countremains0.obj2.countdelegates toproto.countand logs0.
obj1.tags.push('frontend'):- Reading
obj1.tagstraverses toproto.tags. .push()mutates the existing array reference in place onproto. No assignment occurs onobj1.- Both
obj1.tagsandobj2.tagsreference the same shared array onproto.
- Reading
Spoken Explanation & Common Mistakes
- Spoken Summary: "
this.count++performs an assignment (this.count = this.count + 1), which creates an own property onobj1that shadows the prototype value without mutating it. In contrast,tags.push()mutates the shared array on the prototype in place, affecting all instances." - Common Mistake: Expecting
obj1.count++to mutateproto.count, or expectingtags.push()to create an isolated array forobj1."
Q5. Class Inheritance and Field Initializer Order
Difficulty: Mid-Senior
Target Concept: ES6 class field initialization order, super() execution timing, and method polymorphism.
Code Snippet
class Parent {
constructor() {
this.name = 'Parent';
this.printName();
}
printName() {
console.log('Parent print:', this.name);
}
}
class Child extends Parent {
name = 'Child';
printName() {
console.log('Child print:', this.name);
}
}
const inst = new Child();
console.log('Final:', inst.name);
What is the exact console output?
Predicted Output
Child print: Parent
Final: Child
Step-by-Step Execution Trace
new Child()begins instantiation. In derived classes,super()must run before child class field initializers execute.- The
Parentconstructor executes:this.name = 'Parent'sets propertynameon the instance.this.printName()is called.
- Method lookup: Because the instance being constructed is a
Child, polymorphic lookup findsChild.prototype.printName(which overridesParent.prototype.printName). Child.prototype.printNameruns: It accessesthis.name. At this point, child field initializers have not executed yet, butParentconstructor already assignedthis.name = 'Parent'. Logs:Child print: Parent.super()completes: Now the child class field initializers execute.name = 'Child'assigns'Child'tothis.name, overwriting the previous value.console.log('Final:', inst.name)logsFinal: Child.
Spoken Explanation & Common Mistakes
- Spoken Summary: "In class inheritance,
super()runs the parent constructor first, which setsthis.name = 'Parent'. CallingprintName()executes the overridden child method while the instance still holds the parent's property value. Oncesuper()finishes, the child's class field initializername = 'Child'runs and overwritesthis.name." - Common Mistake: Assuming
this.nameis undefined insideprintName(), or assuming child class fields initialize before the parent constructor runs."
Q6. Abstract Equality (==) and ToPrimitive Algorithm
Difficulty: Mid
Target Concept: The ToPrimitive conversion algorithm, valueOf vs toString priority, and equality coercion.
Code Snippet
const obj = {
valueOf() {
return 10;
},
toString() {
return 'hello';
}
};
console.log(obj + 5);
console.log(obj + ' world');
console.log(String(obj));
console.log(`${obj}`);
What is the exact console output?
Predicted Output
15
10 world
hello
hello
Step-by-Step Execution Trace
obj + 5: The binary+operator invokes the ECMAScriptToPrimitivealgorithm with hint"default".- For non-Date objects, hint
"default"behaves as hint"number". - The engine checks for
valueOf(). SincevalueOf()returns primitive10, that value is used. 10 + 5evaluates to numeric addition15. Logs:15.
- For non-Date objects, hint
obj + ' world': The binary+operator again callsToPrimitivewith hint"default".valueOf()returns10.- Now the expression is
10 + ' world'. Because one operand is a string, binary+coerces both to strings and concatenates. Logs:10 world.
String(obj): ExplicitString()conversion callsToPrimitivewith hint"string".- For hint
"string", the engine callstoString()first. toString()returns'hello'. Logs:hello.
- For hint
${obj}: Template literal interpolation callsToPrimitivewith hint"string", executingtoString(). Logs:hello.
Spoken Explanation & Common Mistakes
- Spoken Summary: "The binary
+operator callsToPrimitivewith hint'default', which prioritizesvalueOf()overtoString(). In contrast, string coercion (likeString()or template literals) callsToPrimitivewith hint'string', prioritizingtoString()." - Common Mistake: Assuming string concatenation (
obj + ' world') invokestoString()onobjdirectly. Binary+always callsToPrimitive(default)first, invokingvalueOf()before checking operand types."
Q7. Abstract Equality (==) Corner Cases
Difficulty: Mid
Target Concept: ECMA-262 §7.2.14 Abstract Equality Comparison rules for booleans, arrays, and numbers.
Code Snippet
console.log([] == ![]);
console.log('' == 0);
console.log(null == undefined);
console.log([0] == 0);
console.log(false == '0');
What is the exact console output?
Predicted Output
true
true
true
true
true
Step-by-Step Execution Trace
[] == ![]:![]: The unary logical NOT operator evaluates first. In JavaScript, all objects (including empty arrays) are truthy. Therefore,![]evaluates to booleanfalse.- Expression becomes
[] == false. - When comparing an object to a boolean, the boolean is coerced to a number:
false -> 0. Now[] == 0. - When comparing an object to a number, the object undergoes
ToPrimitive:[].toString()produces"". Now"" == 0. - When comparing string to number, string coerces to number:
Number("") -> 0. - Finally,
0 == 0evaluates totrue.
'' == 0: String''coerces toNumber(''), which is0.0 == 0evaluates totrue.null == undefined: Special rule in the abstract equality specification:nullandundefinedequal each other and nothing else (except themselves). Evaluates totrue.[0] == 0:[0].toString()evaluates to'0'.'0' == 0coerces'0'to0.0 == 0evaluates totrue.false == '0': Booleanfalsecoerces to0. String'0'coerces to0.0 == 0evaluates totrue.
Spoken Explanation & Common Mistakes
- Spoken Summary: "
[] == ![]is true because![]evaluates to booleanfalsefirst. Then abstract equality coerces both sides throughToNumber, turning the array into an empty string\"\"and then into0, whilefalsebecomes0. This is why strict equality (===) is standard production practice." - Common Mistake: Thinking
[] == ![]compares truthiness of both sides.==does not evaluate truthiness; it executes the abstract equality coercion algorithm."
Q8. Unary + and Binary Addition with Primitives and Objects
Difficulty: Junior-Mid
Target Concept: Operator overloading, string concatenation vs numeric coercion, and object stringification.
Code Snippet
console.log('5' - 3);
console.log('5' + 3);
console.log(1 + 2 + '3');
console.log(+true);
console.log(+[]);
console.log(Number({}));
console.log([] + {});
What is the exact console output?
Predicted Output
2
53
33
1
0
NaN
[object Object]
Step-by-Step Execution Trace
'5' - 3: The subtraction operator-only exists for numbers. It coerces'5'to number5.5 - 3 = 2. Logs:2.'5' + 3: The addition operator+is overloaded. Because one operand is a string, it performs string concatenation.3coerces to'3'. Logs:53.1 + 2 + '3': Left-to-right operator associativity:- First,
1 + 2evaluates to numeric3. - Then,
3 + '3'performs string concatenation, resulting in'33'. Logs:33.
- First,
+true: Unary+coerces its operand to a number.Number(true)is1. Logs:1.+[]: Unary+callsToPrimitive([])->""->Number("")->0. Logs:0.Number({}):ToPrimitive({})->"[object Object]"->Number("[object Object]")->NaN. Logs:NaN.[] + {}: Binary+coerces both to primitives:[].toString()is"", and{}.toString()is"[object Object]". Concatenating"" + "[object Object]"yields"[object Object]". Logs:[object Object].
Spoken Explanation & Common Mistakes
- Spoken Summary: "The subtraction operator
-strictly coerces operands to numbers, while binary+defaults to concatenation if either operand is a string. When coercing arrays and objects,[].toString()yields an empty string, while plain objects yield'[object Object]'." - Common Mistake: Expecting
'5' - 3to produce'53'orNaN, forgetting that minus has no string concatenation overload."
Q9. Short-Circuit Evaluation and Nullish Coalescing (??)
Difficulty: Junior
Target Concept: Falsy values (0, "", false, NaN) in logical OR (||) vs nullish values (null, undefined) in ??.
Code Snippet
console.log(0 || 'default');
console.log(0 ?? 'default');
console.log('' || 'fallback');
console.log('' ?? 'fallback');
console.log(null || undefined || 'found');
console.log(null ?? undefined ?? 'found');
What is the exact console output?
Predicted Output
default
0
fallback
found
found
Step-by-Step Execution Trace
0 || 'default': Logical OR (||) checks whether the left-hand operand is truthy. In JavaScript,0is a falsy value. Since0is falsy,||short-circuits to the right-hand operand'default'. Logs:default.0 ?? 'default': Nullish coalescing (??) checks strictly whether the left-hand operand is nullish (nullorundefined). Since0is a defined number (not null or undefined),??returns0. Logs:0.'' || 'fallback': An empty string''is falsy.||returns the fallback'fallback'. Logs:fallback.'' ?? 'fallback': An empty string is not null or undefined.??returns''. Logs an empty line:"".null || undefined || 'found':nullandundefinedare both falsy. Evaluates through to'found'. Logs:found.null ?? undefined ?? 'found':nullandundefinedare both nullish. Evaluates through to'found'. Logs:found.
Spoken Explanation & Common Mistakes
- Spoken Summary: "Logical OR (
||) falls back on any falsy value (0,\"\",false,NaN,null,undefined). The nullish coalescing operator (??) falls back strictly onnullandundefined, preserving valid numerical counts like0and empty text strings\"\"." - Common Mistake: Using
||for default configuration values when0orfalseis a legitimate, intended value (e.g.,timeout = config.timeout || 5000overrides a valid0timeout)."
Q10. Prototype Chain Enumeration vs Object.hasOwn
Difficulty: Mid
Target Concept: Own properties vs inherited prototype properties, the in operator, and Object.hasOwn.
Code Snippet
const baseObj = { role: 'user' };
const userDict = Object.create(baseObj);
userDict.name = 'Alex';
console.log('name' in userDict);
console.log('role' in userDict);
console.log(Object.hasOwn(userDict, 'role'));
console.log(Object.keys(userDict));
What is the exact console output?
Predicted Output
true
true
false
[ 'name' ]
Step-by-Step Execution Trace
Object.create(baseObj):userDictis created with its prototype set tobaseObj.userDict.name = 'Alex': Assigns an own propertynamedirectly touserDict.'name' in userDict: Theinoperator checks whether the property exists on the object or anywhere along its prototype chain. Sincenameis an own property onuserDict, it evaluates totrue. Logs:true.'role' in userDict:roledoes not exist directly onuserDict, so theinoperator traverses the prototype chain tobaseObj. FindingroleonbaseObj, it evaluates totrue. Logs:true.Object.hasOwn(userDict, 'role'):Object.hasOwn()(modern ES2022 replacement forhasOwnProperty) checks strictly whether the property is an own property ofuserDict. Becauseroleis inherited frombaseObj, it evaluates tofalse. Logs:false.Object.keys(userDict): Returns an array of an object's own enumerable string-keyed property names, ignoring inherited prototype properties. Logs:[ 'name' ].
Spoken Explanation & Common Mistakes
- Spoken Summary: "The
inoperator checks both own properties and inherited prototype properties.Object.hasOwn()andObject.keys()check strictly for own properties on the target instance, ignoring inherited prototype chains." - Common Mistake: Using
'prop' in objto check if an object holds its own data, which accidentally returns true for inherited properties liketoStringor prototype defaults."
Front-End Interview Preparation Checklist
Before your next front-end technical interview, verify that you can confidently explain these core JavaScript concepts:
- Call-site
thisResolution: Identify whether a function is called as a method, standalone, or with explicit binding. - Lexical
thisin Arrow Functions: Explain why arrow functions ignorecall/apply/bindand inherit surrounding context. - Hard Binding with
bind(): Understand why the internal[[BoundThis]]slot cannot be overwritten by subsequent binds. - Constructor Returns: Differentiate how returning a primitive vs returning an object impacts the
newoperator. - Property Shadowing: Explain why property assignment (
this.prop = val) creates an own property instead of mutating prototypes. - Class Field Initializers: Understand that
super()runs before child class field initializers execute. -
ToPrimitiveAlgorithm: TracevalueOfvstoStringpriority during binary addition and template literals. - Abstract Equality Coercion: Walk through ECMA-262 type conversion steps for
[] == ![]and'' == 0. -
||vs??Operators: Contrast falsy value fallback with strict nullish (null/undefined) coalescing. - Prototype Reflection: Differentiate the
inoperator fromObject.hasOwnandObject.keys.
Conclusion
Output prediction interview questions evaluate whether an engineer truly understands JavaScript as a runtime system rather than relying on guesswork. By mastering execution contexts, prototype delegation, and type coercion mechanics, you can confidently explain not just what code logs, but the exact ECMAScript rules that govern its behavior.