Skip to content

About

500+ Most Asked JavaScript Output-Based Interview Questions with Detailed Explanations, Thumb Rules, Event Loop, Closures, Hoisting, Promises, Async/Await, and ES6+ Concepts.

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

Β 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 

Repository files navigation

🧠 JavaScript Output-Based Interview Questions (Predict the Output)

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.

πŸ“‚ Category 1: Data Types, Type Coercion & Operators (+, -, ==, ===)

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.

1.1 The + operator with different data types

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" + 1 and 1 + "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), then 3 + "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.


1.2 The + operator with booleans, null, and undefined

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
  • true converts to 1, false converts to 0 when used in math. So true + true = 1 + 1 = 2, true + 1 = 1 + 1 = 2, false + 1 = 0 + 1 = 1.
  • null converts to 0 in math: null + 1 = 0 + 1 = 1. So null + null = 0 + 0 = 0.
  • undefined converts to NaN in math: undefined + 1 = NaN. Anything combined with NaN using + (as numbers) results in NaN. So null + 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.


1.3 The - (subtraction) operator with different types

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" becomes 5 β†’ 5 - 2 = 3
  • "5" - "2" β†’ both convert to numbers β†’ 5 - 2 = 3
  • "5" - "a" β†’ "a" cannot be converted to a number β†’ NaN
  • true - 1 β†’ 1 - 1 = 0; false - 1 β†’ 0 - 1 = -1
  • "10" - true β†’ 10 - 1 = 9
  • null - 1 β†’ 0 - 1 = -1
  • undefined - 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.


1.4 == (loose equality) with primitives β€” the classic gotchas

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" becomes 0 β†’ true
  • 0 == "" β†’ "" becomes 0 β†’ true
  • 0 == false β†’ false becomes 0 β†’ true
  • "" == false β†’ both become 0/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 to 0 when coerced to a number β†’ 0 == 0 β†’ true
  • null == undefined β†’ special case: null and undefined are loosely equal to EACH OTHER and to NOTHING else β†’ true
  • null == 0 β†’ false β€” this surprises many people! null does NOT convert to 0 for == comparisons (even though it does for +/-). null is only == to undefined and itself.
  • undefined == 0 β†’ false β€” same reason, undefined is also only == to null and itself.
  • NaN == NaN β†’ false β€” NaN is 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.


1.5 === (strict equality) β€” no coercion at all

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 β†’ false
  • 0 === false β†’ number vs boolean β†’ false
  • null === undefined β†’ different types β†’ false
  • NaN === NaN β†’ false (special rule, same as ==)
  • 1 === 1.0 β†’ true β€” JavaScript has only ONE number type, so 1 and 1.0 are 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.


1.6 == and === with arrays and objects

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 [] === [] β†’ both false. 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:
    1. ![] is calculated first. [] is a truthy value (all objects are truthy), so ![] = false.
    2. Now the comparison is [] == false.
    3. NOW one side (false) is a primitive, so coercion kicks in: [] converts to a primitive β†’ "" (empty string), and false converts to 0.
    4. "" == 0 β†’ "" converts to 0 β†’ 0 == 0 β†’ true.
  • [] + [] β†’ + converts both arrays to primitives (strings) β†’ "" + "" = "".
  • [] + {} β†’ "" + "[object Object]" = "[object Object]".
  • {} + [] β†’ ⚠️ This one depends on CONTEXT! Inside console.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 to 0. 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!

1.7 Quick-Reference Table: Common Coercion Results

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"

πŸ“‚ Category 2: Hoisting & Scope (var, let, const)

2.1 Hoisting with var

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."


2.2 Hoisting with let (Temporal Dead Zone)

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).


2.3 Function hoisting vs variable hoisting conflict

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.


2.4 Global var vs window object

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).


πŸ“‚ Category 3: Loops, Closures & setTimeout

3.1 var inside a loop with setTimeout

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.


3.2 let inside a loop with setTimeout

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, 3

This 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: 3

Even 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)).


3.3 Closures inside a loop returning functions

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.


3.4 A simple counter closure

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.


πŸ“‚ Category 4: this, Functions & Classes

4.1 this inside a regular function vs arrow 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
  • regular is called as obj.regular(), so this refers to obj, and this.name is "Alex".
  • arrow is an arrow function, which does NOT get its own this. It uses this from where it was DEFINED (the outer/global scope), where name doesn't exist β€” so this.name is undefined.

πŸ”‘ Thumb Rule: Never use arrow functions for object methods if you need this to refer to that object. Use regular function syntax for methods.


4.2 Class fields and detached this

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().


4.3 Function returns vs no return

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.


4.4 Default parameters and undefined

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.


4.5 IIFE (Immediately Invoked Function Expression)

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.


πŸ“‚ Category 5: Truthy/Falsy & Logical Operators

5.1 Truthy and Falsy values

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 {}.


5.2 Short-circuit evaluation with && 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" β†’ 0 is 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" β†’ null is falsy, so && stops immediately and returns null β€” "Never runs" is never evaluated.
  • 1 && 0 && "end" β†’ 1 is truthy (continue), 0 is falsy (STOP here and return 0).

πŸ”‘ 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.


5.3 NaN comparisons

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().


5.4 Increment operator confusion

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 increments x to 6.
  • console.log(x) now shows 6 (already incremented).
  • ++x (pre-increment) increments x to 7 FIRST, THEN returns the new value (7).
  • console.log(x) confirms x is 7.

πŸ”‘ 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); // 4

Step 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.


5.5 Nested ternary operators

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 β†’ false
  • age < 18? 20 < 18 β†’ false
  • age < 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.)


πŸ“‚ Category 6: Arrays & Objects

6.1 Array vs Object reference (copy by reference)

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.


6.2 Object comparison

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).


6.3 Spread operator β€” shallow copy gotcha

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 modifying copy[1] (the nested array) also affects arr[1].

πŸ”‘ Thumb Rule: Spread (...) only copies ONE level deep. Nested objects/arrays are still shared between the original and the copy.


6.4 Object key order and duplicate keys

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).


6.5 Array.length after deleting an element

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).


6.6 Chained array methods

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:

  1. .filter(n => n % 2 === 0) β†’ keeps even numbers β†’ [2, 4, 6]
  2. .map(n => n * 10) β†’ multiplies each by 10 β†’ [20, 40, 60]
  3. .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.


6.7 Array sort() default behavior

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.


6.8 typeof a function and typeof an array

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.


6.9 String comparison (number-like strings)

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.


πŸ“‚ Category 7: Async, Promises & Event Loop

7.1 Order of console.log with Promises and setTimeout

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:

  1. console.log("1") and console.log("4") run immediately β€” they're part of the "main" synchronous code.
  2. Promise.then() callbacks go into the microtask queue.
  3. setTimeout callbacks go into the macrotask (callback) queue.
  4. 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).


7.2 Async function return value

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.


7.3 Sequential vs parallel await

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 total

Here, 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.


7.4 Try/catch/finally execution order

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.).


🎯 Master List of Thumb Rules (Quick Recap)

# 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.

πŸ’‘ General Strategy for Output-Based Questions

  1. Read the code top to bottom first β€” don't jump around.
  2. 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).
  3. Track variable values step-by-step β€” write down each variable's value as it changes, especially with ++/-- or reassignments.
  4. Watch for type coercion β€” anytime +, -, ==, or comparison operators mix different types (string/number/boolean/object).
  5. Check scope β€” is the variable var, let, or const? Is it inside a loop, function, or block?
  6. When in doubt, say your reasoning out loud β€” even if you get the final answer wrong, interviewers give credit for correct REASONING and approach.

🀝 Contributing

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.

⭐ Support

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! πŸŽ‰


About

500+ Most Asked JavaScript Output-Based Interview Questions with Detailed Explanations, Thumb Rules, Event Loop, Closures, Hoisting, Promises, Async/Await, and ES6+ Concepts.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors