This file covers the "What will this code print?" style questions β one of the most common formats in JavaScript interviews. These questions test whether you truly understand how JavaScript works under the hood, not just whether you've memorized definitions.
π‘ How to use this file
- For each question, read the code FIRST and try to guess the output yourself.
- Then check the actual output and explanation below it.
- Pay special attention to the π Thumb Rule β it's the "shortcut" that helps you solve similar questions instantly, even if you've never seen that exact code before.
This is the most important category β almost every JS interview has at least 2-3 questions from here. The core idea: JavaScript tries to be "helpful" by automatically converting (coercing) values from one type to another, and this leads to some surprising results.
console.log(1 + 1); // ?
console.log("1" + 1); // ?
console.log(1 + "1"); // ?
console.log("1" + "1"); // ?
console.log(1 + 2 + "3"); // ?
console.log("1" + 2 + 3); // ?π View Output
2
11
11
11
33
123
π View Explanation
1 + 1β both numbers β normal math β2"1" + 1and1 + "1"β one side is a string β the NUMBER is converted to a string, and they're joined together β"11""1" + "1"β both strings β joined together β"11"1 + 2 + "3"β evaluated LEFT to RIGHT:1 + 2 = 3(both numbers, normal math), then3 + "3" = "33"(now a string appears, so it becomes concatenation)"1" + 2 + 3β"1" + 2 = "12"(string appears immediately), then"12" + 3 = "123"
π Thumb Rule: The + operator works LEFT to RIGHT. The moment a string appears anywhere in the chain, everything from that point onward becomes string concatenation β but operations done BEFORE that point (if both sides were numbers) remain normal math.
console.log(true + true); // ?
console.log(true + 1); // ?
console.log(false + 1); // ?
console.log(null + 1); // ?
console.log(undefined + 1); // ?
console.log(null + undefined); // ?
console.log(null + null); // ?π View Output
2
2
1
1
NaN
NaN
0
π View Explanation
trueconverts to1,falseconverts to0when used in math. Sotrue + true = 1 + 1 = 2,true + 1 = 1 + 1 = 2,false + 1 = 0 + 1 = 1.nullconverts to0in math:null + 1 = 0 + 1 = 1. Sonull + null = 0 + 0 = 0.undefinedconverts toNaNin math:undefined + 1 = NaN. Anything combined withNaNusing+(as numbers) results inNaN. Sonull + undefinedβ0 + NaN = NaN.
π Thumb Rule: For arithmetic, true β 1, false β 0, null β 0, but undefined β NaN. Remember: null behaves like 0, but undefined "poisons" any math operation into NaN.
console.log("5" - 2); // ?
console.log("5" - "2"); // ?
console.log("5" - "a"); // ?
console.log(true - 1); // ?
console.log(false - 1); // ?
console.log("10" - true); // ?
console.log(null - 1); // ?
console.log(undefined - 1);// ?π View Output
3
3
NaN
0
-1
9
-1
NaN
π View Explanation
Unlike +, the - operator has NO "string concatenation" mode β it ALWAYS tries to convert both sides to numbers and subtract.
"5" - 2β"5"becomes5β5 - 2 = 3"5" - "2"β both convert to numbers β5 - 2 = 3"5" - "a"β"a"cannot be converted to a number βNaNtrue - 1β1 - 1 = 0;false - 1β0 - 1 = -1"10" - trueβ10 - 1 = 9null - 1β0 - 1 = -1undefined - 1βNaN - 1 = NaN
π Thumb Rule: + is special because it ALSO means "string concatenation." Every OTHER math operator (-, *, /, %) has no such special case β they ALWAYS convert both operands to numbers first.
console.log(0 == "0"); // ?
console.log(0 == ""); // ?
console.log(0 == false); // ?
console.log("" == false); // ?
console.log("" == "0"); // ?
console.log(" " == 0); // ?
console.log(null == undefined); // ?
console.log(null == 0); // ?
console.log(undefined == 0); // ?
console.log(NaN == NaN); // ?π View Output
true
true
true
true
false
true
true
false
false
false
π View Explanation
== converts both sides to a common type before comparing. Walking through the tricky ones:
0 == "0"β"0"becomes0βtrue0 == ""β""becomes0βtrue0 == falseβfalsebecomes0βtrue"" == falseβ both become0/falsy-numeric βtrue"" == "0"β BOTH are strings, so NO coercion happens, and""is literally not the same text as"0"βfalse" " == 0β a whitespace-only string converts to0when coerced to a number β0 == 0βtruenull == undefinedβ special case:nullandundefinedare loosely equal to EACH OTHER and to NOTHING else βtruenull == 0βfalseβ this surprises many people!nulldoes NOT convert to0for==comparisons (even though it does for+/-).nullis only==toundefinedand itself.undefined == 0βfalseβ same reason,undefinedis also only==tonulland itself.NaN == NaNβfalseβNaNis never equal to anything, including itself, by definition.
π Thumb Rule: null and undefined are == to EACH OTHER and to NOTHING ELSE (not even 0, false, or "") β this is a special hardcoded rule in JavaScript, completely separate from normal coercion rules.
console.log(0 === "0"); // ?
console.log(0 === false); // ?
console.log(null === undefined); // ?
console.log(NaN === NaN); // ?
console.log(1 === 1.0); // ?
console.log("abc" === "abc"); // ?π View Output
false
false
false
false
true
true
π View Explanation
=== checks BOTH the value AND the type β if the types differ, it's false immediately, no conversion attempted.
0 === "0"β number vs string βfalse0 === falseβ number vs boolean βfalsenull === undefinedβ different types βfalseNaN === NaNβfalse(special rule, same as==)1 === 1.0βtrueβ JavaScript has only ONE number type, so1and1.0are literally the same value."abc" === "abc"β same type, same value βtrue
π Thumb Rule: === = "same type AND same value." If you're ever unsure about == behavior, just remember: === never does conversions, so it's predictable. Always prefer === in real code.
console.log([] == []); // ?
console.log([] === []); // ?
console.log([] == {}); // ?
console.log([] == ![]); // ?
console.log([] + []); // ?
console.log([] + {}); // ?
console.log({} + []); // ? (run this in browser console vs Node β tricky!)
console.log([1,2] + [3,4]); // ?π View Output
false
false
false
true
""
[object Object]
[object Object]
1,23,4
π View Explanation
[] == []and[] === []β bothfalse. When BOTH sides of==/===are objects (arrays count as objects), there is NO coercion β it's purely a reference check. Two different[]literals create two different array objects in memory.[] == {}βfalseβ same reason, two different objects, no coercion between two objects.[] == ![]βtrueπ±. Here's the trick:![]is calculated first.[]is a truthy value (all objects are truthy), so![]=false.- Now the comparison is
[] == false. - NOW one side (
false) is a primitive, so coercion kicks in:[]converts to a primitive β""(empty string), andfalseconverts to0. "" == 0β""converts to0β0 == 0βtrue.
[] + []β+converts both arrays to primitives (strings) β"" + "" = "".[] + {}β"" + "[object Object]" = "[object Object]".{} + []ββ οΈ This one depends on CONTEXT! Insideconsole.log({} + []),{}is treated as an OBJECT (since it's an expression/argument), so it behaves like[] + {}above β"[object Object]". But if written as a STANDALONE statement on its own line ({} + []at the start of a line), JavaScript may interpret{}as an empty BLOCK statement, and+[]as a separate expression that evaluates to0. This is a famous "it depends how you write it" trap.[1,2] + [3,4]β both arrays convert to strings:"1,2" + "3,4" = "1,23,4".
π Thumb Rule:
- Object vs Object (for
==or===) β always reference comparison, coercion NEVER happens. - Object vs Primitive β the object gets converted to a primitive (arrays β comma-joined string, plain objects β
"[object Object]"), THEN normal coercion rules apply. {} + []is famous specifically because of the ambiguity between "block statement" vs "object literal" β always wrap in parentheses({} + [])if you genuinely need this, but more importantly: never write code like this in production!
| Expression | Result | Why |
|---|---|---|
1 + "2" |
"12" |
number β string, concatenation |
"5" - 1 |
4 |
string β number, subtraction |
"5" * "2" |
10 |
both strings β numbers |
true + true |
2 |
true β 1 |
"" + 1 |
"1" |
number β string |
null + 1 |
1 |
null β 0 |
undefined + 1 |
NaN |
undefined β NaN |
0 == false |
true |
both β 0 |
0 === false |
false |
different types |
null == undefined |
true |
special rule |
null === undefined |
false |
different types |
NaN == NaN |
false |
NaN β NaN, ever |
[] == [] |
false |
different references |
[1] == "1" |
true |
array β "1", then "1" == "1" |
console.log(a);
var a = 5;
console.log(a);π View Output
undefined
5
π View Explanation
JavaScript "hoists" the DECLARATION of var a to the top of the scope, but NOT the assignment (= 5). So at the first console.log, a exists but has no value yet (undefined). After the assignment line runs, a becomes 5.
π Thumb Rule: With var, think of it as: "the variable name moves to the top, but the value stays where it is."
console.log(b);
let b = 5;π View Output
ReferenceError: Cannot access 'b' before initialization
π View Explanation
let (and const) are also hoisted, but they are NOT usable before their declaration line. This gap between the start of the scope and the declaration line is called the Temporal Dead Zone (TDZ). Accessing the variable during this zone throws an error.
π Thumb Rule: var before declaration β undefined (no error). let/const before declaration β ReferenceError (TDZ).
var greet = "Hello";
function greet() {
return "Hi";
}
console.log(typeof greet); // ?π View Output
string
π View Explanation
Both function declarations AND var declarations are hoisted, but function declarations are hoisted "more strongly" β they're hoisted WITH their full definition. However, the code then runs top-to-bottom, and var greet = "Hello" executes AFTER the function declaration is hoisted, OVERWRITING greet with the string "Hello". So by the time console.log runs, greet is a string.
π Thumb Rule: When a var and a function have the SAME name, the function declaration is hoisted first, but any LATER assignment (even with var) will overwrite it once the code actually executes in order.
var name = "Global";
let age = 25;
console.log(window.name); // ? (in a browser environment)
console.log(window.age); // ?π View Output
Global
undefined
π View Explanation
In browsers, variables declared with var at the top level (global scope) become properties of the window object. Variables declared with let or const do NOT become properties of window β they exist in a separate "script scope," even though they're still globally accessible by name.
π Thumb Rule: var at global scope pollutes the global window object. let/const at global scope do not β this is one reason let/const are preferred (cleaner global scope, fewer naming collisions).
for (var i = 1; i <= 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}π View Output
4
4
4
π View Explanation
var does NOT create a new variable for each loop iteration β there is only ONE i shared across all iterations, stored in the function/global scope. By the time setTimeout actually runs (after 1 second), the loop has already finished completely, and i has become 4 (the value that made the loop condition false and exit).
π Thumb Rule: var is function-scoped, NOT block-scoped. So loops using var share a single variable across all iterations β by the time any delayed code runs, the loop has already finished and the variable holds its FINAL value.
for (let i = 1; i <= 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}π View Output
1
2
3
π View Explanation
Unlike var, let is block-scoped β JavaScript creates a NEW i for EACH iteration of the loop. So each setTimeout callback "remembers" its own separate copy of i (this is a closure).
π Thumb Rule: If you ever see var causing unexpected "same value repeated" output in a loop, the fix is almost always: replace var with let.
π Variation β fixing var WITHOUT switching to let (using an IIFE):
for (var i = 1; i <= 3; i++) {
(function(j) {
setTimeout(() => console.log(j), 1000);
})(i);
}
// Output: 1, 2, 3This works because the IIFE creates a new j for each iteration β an old-school trick used before let existed.
π Variation β same problem, written slightly differently:
function printNumbers() {
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log("Number:", i);
}, i * 1000);
}
}
printNumbers();
// Output: Number: 3 / Number: 3 / Number: 3Even though each setTimeout fires at a DIFFERENT time (0s, 1s, 2s), by the time ANY of them run, the loop has already finished and i is 3. The fix is the same: use let, or pass i as a third argument to setTimeout (setTimeout(fn, i*1000, i)).
function createFunctions() {
let funcs = [];
for (let i = 0; i < 3; i++) {
funcs.push(() => console.log(i));
}
return funcs;
}
const myFuncs = createFunctions();
myFuncs[0](); // ?
myFuncs[1](); // ?
myFuncs[2](); // ?π View Output
0
1
2
π View Explanation
Because let creates a new i for each loop iteration, each arrow function in the array "closes over" (remembers) its OWN separate i. If var were used instead, all three would print 3.
π Thumb Rule: Same root cause as 3.1/3.2 β let in loops = separate variable per iteration = each closure remembers its own value.
function outerFunction() {
let count = 0;
return function innerFunction() {
count++;
console.log(count);
};
}
const counter = outerFunction();
counter(); // ?
counter(); // ?
counter(); // ?π View Output
1
2
3
π View Explanation
Even though outerFunction() has already finished executing, innerFunction still "remembers" the count variable from its parent scope. Each call to counter() updates the SAME count variable, because it's the same closure being reused.
π Thumb Rule: A closure "remembers" the variables from where it was CREATED, not where it's CALLED β and that memory persists across multiple calls if you keep reusing the same returned function.
const obj = {
name: "Alex",
regular: function() {
console.log("Regular:", this.name);
},
arrow: () => {
console.log("Arrow:", this.name);
}
};
obj.regular(); // ?
obj.arrow(); // ?π View Output
Regular: Alex
Arrow: undefined
π View Explanation
regularis called asobj.regular(), sothisrefers toobj, andthis.nameis"Alex".arrowis an arrow function, which does NOT get its ownthis. It usesthisfrom where it was DEFINED (the outer/global scope), wherenamedoesn't exist β sothis.nameisundefined.
π Thumb Rule: Never use arrow functions for object methods if you need this to refer to that object. Use regular function syntax for methods.
class Counter {
count = 0;
increment() {
this.count++;
console.log(this.count);
}
}
const counter = new Counter();
const incrementFn = counter.increment;
counter.increment(); // ?
incrementFn(); // ?π View Output
1
TypeError: Cannot read properties of undefined (reading 'count')
π View Explanation
counter.increment() works fine because this refers to counter. But once you extract the method into a standalone variable (incrementFn) and call it WITHOUT the object (incrementFn()), this is no longer bound to counter β it becomes undefined (in strict mode/classes), causing an error when trying to access this.count.
π Thumb Rule: this depends on HOW a function is CALLED, not where it's defined. If you "detach" a method from its object and call it standalone, this is lost. Fix using .bind(), arrow functions for class fields, or by always calling it as obj.method().
function noReturn() {
console.log("Hello");
}
let result = noReturn();
console.log(result); // ?π View Output
Hello
undefined
π View Explanation
The function noReturn doesn't have a return statement, so it implicitly returns undefined. The console.log("Hello") still runs (that's its side effect), but the VALUE returned and stored in result is undefined.
π Thumb Rule: A function with no explicit return ALWAYS returns undefined β even if it does other things like printing to the console.
function greet(name = "Guest") {
console.log(`Hello, ${name}`);
}
greet(); // ?
greet(undefined); // ?
greet(null); // ?
greet(""); // ?π View Output
Hello, Guest
Hello, Guest
Hello, null
Hello,
π View Explanation
Default parameters are used ONLY when the argument is undefined (or not passed at all). Passing null or "" (empty string) are still "real values" β they don't trigger the default.
π Thumb Rule: Default parameters only kick in for undefined, not for null, 0, "", or false.
var result = (function() {
var x = 10;
return x * 2;
})();
console.log(result); // ?
console.log(typeof x); // ?π View Output
20
undefined
π View Explanation
An IIFE runs immediately after it's defined. The variable x is local to the IIFE's scope and doesn't leak outside β so result gets 20 (the returned value), but x doesn't exist in the outer scope. Using typeof on a completely undeclared variable returns "undefined" instead of throwing an error (this is a special safety behavior of typeof).
π Thumb Rule: IIFEs create a private scope β variables declared inside never leak out. Also remember: typeof someUndeclaredVariable is safe and returns "undefined", but actually USING that variable (e.g., console.log(someUndeclaredVariable)) throws a ReferenceError.
const values = [0, "", null, undefined, NaN, false, "0", [], {}];
values.forEach(val => {
console.log(Boolean(val));
});π View Output
false
false
false
false
false
false
true
true
true
π View Explanation
There are only 6 falsy values in JavaScript: 0, "" (empty string), null, undefined, NaN, and false. EVERYTHING else is truthy β including "0" (a non-empty string!), empty arrays [], and empty objects {} (because they are objects, and all objects are truthy).
π Thumb Rule: Memorize the 6 falsy values: 0, "", null, undefined, NaN, false. If a value isn't on this list, it's truthy β even "0", [], and {}.
console.log(0 || "default"); // ?
console.log("" || "fallback"); // ?
console.log("Hi" && "Bye"); // ?
console.log(null && "Never runs"); // ?
console.log(1 && 0 && "end"); // ?π View Output
default
fallback
Bye
null
0
π View Explanation
||(OR) returns the FIRST truthy value it finds (or the last value if all are falsy).0 || "default"β0is falsy, so it returns"default".&&(AND) returns the FIRST falsy value it finds (or the last value if all are truthy)."Hi" && "Bye"β both truthy, so it returns the LAST one,"Bye".null && "Never runs"βnullis falsy, so&&stops immediately and returnsnullβ"Never runs"is never evaluated.1 && 0 && "end"β1is truthy (continue),0is falsy (STOP here and return0).
π Thumb Rule: || returns the first TRUTHY value (or the last value). && returns the first FALSY value (or the last value). Both operators return an actual VALUE, not just true/false.
let result = 10 / "abc";
console.log(result); // ?
console.log(result === NaN); // ?
console.log(Number.isNaN(result)); // ?
console.log(isNaN("hello")); // ?π View Output
NaN
false
true
true
π View Explanation
Dividing a number by a non-numeric string produces NaN ("Not a Number"). But NaN === NaN is always false β NaN is the only value in JavaScript that is NOT equal to itself. The correct way to check is Number.isNaN().
isNaN("hello") returns true too, but isNaN() (the global function) first tries to CONVERT the value to a number before checking β so it can give misleading results for non-number inputs. Number.isNaN() is stricter and safer.
π Thumb Rule: To check if a value is NaN, always use Number.isNaN(value), never value === NaN or the loose global isNaN().
let x = 5;
console.log(x++); // ?
console.log(x); // ?
console.log(++x); // ?
console.log(x); // ?π View Output
5
6
7
7
π View Explanation
x++(post-increment) returns the CURRENT value (5), THEN incrementsxto6.console.log(x)now shows6(already incremented).++x(pre-increment) incrementsxto7FIRST, THEN returns the new value (7).console.log(x)confirmsxis7.
π Thumb Rule: "Post" (x++) = use OLD value, then increase. "Pre" (++x) = increase FIRST, then use NEW value. Say it as: "post means the increment happens AFTER it's used."
π Variation β combining both in one expression:
let a = 1;
let b = a++ + ++a;
console.log(a); // 3
console.log(b); // 4Step by step: a++ returns 1 (then a becomes 2). ++a makes a become 3 first, then returns 3. So b = 1 + 3 = 4, and final a = 3.
let age = 20;
let category = age < 13 ? "Child"
: age < 18 ? "Teenager"
: age < 60 ? "Adult"
: "Senior";
console.log(category); // ?π View Output
Adult
π View Explanation
Ternary operators (condition ? valueIfTrue : valueIfFalse) can be chained. JavaScript checks each condition in order:
age < 13?20 < 13β falseage < 18?20 < 18β falseage < 60?20 < 60β true β returns"Adult"
The remaining conditions are never checked once a true is found.
π Thumb Rule: Nested ternaries work like an if / else if / else if / else chain. Read them top to bottom, and the FIRST true condition "wins." (Deeply nested ternaries are discouraged in production code for readability β if/else or switch is often clearer.)
let arr1 = [1, 2, 3];
let arr2 = arr1;
arr2.push(4);
console.log(arr1); // ?
console.log(arr2); // ?π View Output
[1, 2, 3, 4]
[1, 2, 3, 4]
π View Explanation
Arrays and objects in JavaScript are stored "by reference" β arr2 = arr1 doesn't create a NEW array, it just makes arr2 point to the SAME array in memory as arr1. So changing arr2 also changes arr1, because they're literally the same object.
π Thumb Rule: Primitives (numbers, strings, booleans) are copied BY VALUE. Objects and arrays are copied BY REFERENCE. To make a real independent copy, use [...arr1], {...obj1}, or a deep clone for nested data.
const obj1 = { name: "Tom" };
const obj2 = { name: "Tom" };
const obj3 = obj1;
console.log(obj1 == obj2); // ?
console.log(obj1 === obj2); // ?
console.log(obj1 === obj3); // ?π View Output
false
false
true
π View Explanation
Objects are compared by REFERENCE (memory address), not by their contents. obj1 and obj2 look identical but are two SEPARATE objects in memory β so they are NOT equal. obj3, however, points to the EXACT SAME object as obj1 (obj3 = obj1), so they ARE equal.
π Thumb Rule: Two objects/arrays are only === equal if they are the SAME object in memory (same reference) β never because they "look" the same. To compare contents, you need a deep-equality check (e.g., JSON.stringify(obj1) === JSON.stringify(obj2) for simple cases, or a library like Lodash's isEqual for complex cases).
let str = "abc";
let chars = [...str];
console.log(chars); // ?
let arr = [1, [2, 3], 4];
let copy = [...arr];
copy[1].push(99);
console.log(arr); // ?π View Output
['a', 'b', 'c']
[1, [2, 3, 99], 4]
π View Explanation
[...str]spreads a string into an array of its individual characters.[...arr]creates a shallow copy β the top-level array is new, but nested arrays/objects INSIDE it still point to the SAME memory as the original. So modifyingcopy[1](the nested array) also affectsarr[1].
π Thumb Rule: Spread (...) only copies ONE level deep. Nested objects/arrays are still shared between the original and the copy.
const obj = {
a: 1,
b: 2,
a: 3
};
console.log(obj); // ?
console.log(Object.keys(obj)); // ?π View Output
{ a: 3, b: 2 }
['a', 'b']
π View Explanation
If an object has duplicate keys, JavaScript silently keeps only the LAST value for that key β no error is thrown. So a: 1 is overwritten by a: 3.
π Thumb Rule: Duplicate object keys = last one wins, with no warning. Always double-check object literals for accidental duplicate keys (easy to miss in large objects).
let arr = [1, 2, 3, 4, 5];
delete arr[2];
console.log(arr); // ?
console.log(arr.length); // ?π View Output
[1, 2, <1 empty item>, 4, 5]
5
π View Explanation
delete removes the VALUE at that index but does NOT shift other elements or update length. It leaves a "hole" (empty slot) in the array. This is almost never what you want.
π Thumb Rule: Never use delete on array elements. To properly remove an item, use splice() (which shifts elements and updates length) or filter() (which creates a new array without that item).
const numbers = [1, 2, 3, 4, 5, 6];
const result = numbers
.filter(n => n % 2 === 0)
.map(n => n * 10)
.reduce((sum, n) => sum + n, 0);
console.log(result); // ?π View Output
120
π View Explanation
Step by step:
.filter(n => n % 2 === 0)β keeps even numbers β[2, 4, 6].map(n => n * 10)β multiplies each by 10 β[20, 40, 60].reduce((sum, n) => sum + n, 0)β adds them all together β20 + 40 + 60 = 120
π Thumb Rule: When you see chained array methods, work through them ONE AT A TIME, writing down the resulting array after each step β don't try to do it all in your head at once.
const numbers = [10, 1, 21, 2];
console.log(numbers.sort()); // ?π View Output
[1, 10, 2, 21]
π View Explanation
By default, .sort() converts elements to STRINGS and sorts them alphabetically/lexicographically β NOT numerically. So "10" comes before "2" because "1" (first character) is less than "2".
π Thumb Rule: ALWAYS pass a comparator function when sorting numbers: numbers.sort((a, b) => a - b) for ascending order. Without it, .sort() treats everything as strings, which gives wrong results for numbers β₯ 10.
function sayHi() {}
const arr = [1, 2, 3];
const arrowFn = () => {};
console.log(typeof sayHi); // ?
console.log(typeof arr); // ?
console.log(typeof arrowFn); // ?
console.log(Array.isArray(arr)); // ?
console.log(typeof null); // ?π View Output
function
object
function
true
object
π View Explanation
Functions (whether regular or arrow) have their own special typeof result: "function". Arrays, despite being a distinct "thing" conceptually, return "object" for typeof β you need Array.isArray() to specifically check for arrays. typeof null is also "object" β a famous long-standing bug in JS that can never be fixed now.
π Thumb Rule: typeof has only 8 possible results: "undefined", "object", "boolean", "number", "string", "bigint", "symbol", and "function". Arrays AND null both fall under "object" β always double-check with Array.isArray() or === null when it matters.
console.log("apple" < "banana"); // ?
console.log("Apple" < "apple"); // ?
console.log("10" < "9"); // ?
console.log(10 < 9); // ?π View Output
true
true
true
false
π View Explanation
Strings are compared character by character based on their Unicode/ASCII values, like a dictionary, but with a twist: uppercase letters have SMALLER character codes than lowercase letters. So "A" (65) comes before "a" (97), making "Apple" < "apple" β true.
For "10" < "9", these are STRINGS, so they're compared character by character: "1" (code 49) is less than "9" (code 57), so "10" < "9" is true β even though numerically 10 is greater than 9!
π Thumb Rule: Comparing strings that LOOK like numbers does NOT do numeric comparison β it compares character-by-character. Always convert to Number() first if you want numeric comparison.
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("4");π View Output
1
4
3
2
π View Explanation
This tests your understanding of the Event Loop and its different queues:
console.log("1")andconsole.log("4")run immediately β they're part of the "main" synchronous code.Promise.then()callbacks go into the microtask queue.setTimeoutcallbacks go into the macrotask (callback) queue.- After the main code finishes, JavaScript ALWAYS empties the microtask queue completely before moving to the macrotask queue.
So the order is: synchronous code first (1, 4), then microtasks (3), then macrotasks (2).
π Thumb Rule: Synchronous code β Microtasks (Promises) β Macrotasks (setTimeout/setInterval). Promises always "jump the queue" ahead of setTimeout, even with setTimeout(fn, 0).
async function getValue() {
return 42;
}
const result = getValue();
console.log(result); // ?π View Output
Promise { 42 }
π View Explanation
An async function ALWAYS returns a Promise β even if you write a plain return 42, JavaScript automatically wraps it as Promise.resolve(42). To get the actual value 42, you'd need to use .then() or await it inside another async function.
π Thumb Rule: Calling an async function NEVER gives you the "final value" directly β it always gives you a Promise. You must use .then() or await to unwrap it.
function delay(value, ms) {
return new Promise(resolve => setTimeout(() => resolve(value), ms));
}
async function sequential() {
const a = await delay("A", 1000);
const b = await delay("B", 1000);
console.log(a, b);
}
sequential(); // How long does this take in total?Output (timing):
Takes about 2 seconds total, then prints: A B
π View Explanation
Each await PAUSES the function until that Promise resolves. Since b's delay doesn't start until AFTER a finishes, the two 1-second delays happen one after another β totaling about 2 seconds. This is called sequential execution.
π Variation β running them in PARALLEL instead:
async function parallel() {
const [a, b] = await Promise.all([delay("A", 1000), delay("B", 1000)]);
console.log(a, b);
}
parallel(); // Takes only about 1 second totalHere, both delay() calls START at the same time, and Promise.all() waits for BOTH to finish β so the total time is only as long as the SLOWER one (1 second), not the sum.
π Thumb Rule: If tasks don't depend on each other's results, start them all FIRST (without await), then use Promise.all() to wait for all of them β this is much faster than awaiting one-by-one.
function test() {
try {
console.log("Try");
throw new Error("Oops");
} catch (err) {
console.log("Catch:", err.message);
} finally {
console.log("Finally");
}
console.log("After try-catch");
}
test();π View Output
Try
Catch: Oops
Finally
After try-catch
π View Explanation
The try block runs first and throws an error. The catch block catches it and runs. The finally block ALWAYS runs β whether or not an error occurred, and even if there's a return inside try or catch. Code AFTER the try-catch-finally block continues normally (since the error was already caught/handled).
π Thumb Rule: finally ALWAYS runs β no matter what happens in try or catch (including return statements). It's typically used for cleanup tasks (closing files, hiding loaders, etc.).
| # | Rule |
|---|---|
| 1 | The + operator: once a string appears, everything after becomes string concatenation. |
| 2 | For math, trueβ1, falseβ0, nullβ0, but undefinedβNaN. |
| 3 | -, *, /, % ALWAYS convert both sides to numbers β they have no "concatenation" mode like +. |
| 4 | null == undefined is true, but BOTH are == to NOTHING else (not even 0, "", or false). |
| 5 | === never converts types β always prefer it over ==. |
| 6 | Object vs Object comparisons (==/===) are ALWAYS reference checks β coercion never applies between two objects. |
| 7 | Object vs Primitive comparisons convert the object to a primitive first (arrays β joined string, objects β "[object Object]"), then coercion applies. |
| 8 | var is function-scoped and shared across loop iterations β use let for per-iteration values. |
| 9 | var before declaration = undefined. let/const before declaration = ReferenceError (TDZ). |
| 10 | typeof null is "object" (a known JS bug). Use Array.isArray() to detect arrays. |
| 11 | Avoid ==; to check NaN, use Number.isNaN(), never value === NaN. |
| 12 | Event loop order: Synchronous code β Microtasks (Promises) β Macrotasks (setTimeout). |
| 13 | Arrow functions don't have their own this β they inherit it from where they're defined. |
| 14 | Objects/arrays are copied by REFERENCE; primitives are copied by VALUE. |
| 15 | Only 6 falsy values: 0, "", null, undefined, NaN, false. Everything else is truthy. |
| 16 | x++ uses the OLD value then increments; ++x increments FIRST then uses the new value. |
| 17 | String comparisons are character-by-character, NOT numeric β even for number-like strings. |
| 18 | Default parameters trigger ONLY for undefined, not null/0/"". |
| 19 | Spread (...) creates a SHALLOW copy β nested objects/arrays are still shared. |
| 20 | && returns the first falsy value (or last value); ` |
| 21 | An async function always returns a Promise, even if you return a plain value. |
| 22 | delete on array elements leaves holes β use splice() or filter() instead. |
| 23 | .sort() without a comparator sorts as STRINGS β always pass (a, b) => a - b for numbers. |
| 24 | finally always runs, no matter what happens in try/catch. |
| 25 | this depends on HOW a function is called β detaching a method from its object loses this. |
- Read the code top to bottom first β don't jump around.
- Identify if async code is involved (Promises,
setTimeout,async/await) β if so, separate the SYNCHRONOUS lines from the ASYNCHRONOUS ones and figure out their order using the event loop rules (7.1). - Track variable values step-by-step β write down each variable's value as it changes, especially with
++/--or reassignments. - Watch for type coercion β anytime
+,-,==, or comparison operators mix different types (string/number/boolean/object). - Check scope β is the variable
var,let, orconst? Is it inside a loop, function, or block? - When in doubt, say your reasoning out loud β even if you get the final answer wrong, interviewers give credit for correct REASONING and approach.
Found a mistake or want to add more questions? Feel free to open an Issue or submit a Pull Request β this repo is meant to grow with help from the community.
If this repo helped you in your interview prep, consider giving it a β star β it helps others find it too!
Good luck β these questions become easy with practice! π