A comprehensive reference covering core JS concepts, async patterns, array methods, and output-based questions.
- Core Concepts
- 1. == vs === Operators
- 2. Hoisting
- 3. Scope
- 4. Undefined Property
- 5. Null Value
- 6. Null vs Undefined
- 7. Window vs Document
- 8. isNaN
- 9. Undeclared vs Undefined Variables
- 10. Global Variables
- 11. Compiled vs Interpreted
- 12. Case Sensitivity
- 13. Java vs JavaScript
- 14. Single Threaded
- 15. ECMAScript
- 16. Primitive vs Reference Types
- 17. Detecting Primitive vs Non-Primitive
- 18. isNaN vs Number.isNaN
- 19. Lexical Scope
- Functions
- Variables & Scope
- Execution Model
- Async JavaScript
- this Keyword
- Objects
- Events
- Other Concepts
- Array Methods
- Output-Based Questions
== (loose equality) performs type coercion before comparing. === (strict equality) compares both value and type — no coercion.
0 == false // true — coercion: false → 0
0 === false // false — different types
1 == "1" // true — string "1" → number 1
1 === "1" // false
null == undefined // true — special case
null === undefined// false
'0' == false // true — '0' → 0 → false
NaN == NaN // false — NaN is never equal to itself
[] == [] // false — different object references
{} == {} // false — different object referencesHoisting is JavaScript's default behavior where variable and function declarations are moved to the top of their scope before code executes.
console.log(message); // undefined (var is hoisted)
var message = "The variable has been hoisted";
// Function declarations are fully hoisted:
message("Good morning"); // works fine
function message(name) {
console.log(name);
}Scope defines where a variable can be accessed in JavaScript. There are three types:
1. Global Scope — accessible anywhere in the code.
let city = "Delhi";
function test() {
console.log(city); // Delhi
}2. Function Scope — accessible only inside the function.
function demo() {
var age = 22;
console.log(age); // 22
}
console.log(age); // ReferenceError3. Block Scope — accessible only within curly braces {}.
{
let a = 10;
const b = 20;
}
console.log(a); // ReferenceError
console.log(b); // ReferenceErrorIf you access a property that does not exist on an object, JavaScript returns undefined.
null means intentionally assigned empty value. It is explicitly set by the developer to indicate "no value."
null |
undefined |
|---|---|
| Intentionally empty value | Variable declared but not assigned |
Type: "object" (JS quirk) |
Type: "undefined" |
null == undefined → true |
null === undefined → false |
Window
- Root-level element in any web page.
- Available implicitly by default.
- Has methods like
alert(),confirm()and properties likedocument,location.
Document
- Direct child of the
windowobject (also known as the DOM). - Accessible via
window.documentor justdocument. - Provides methods like
getElementById,createElement, etc.
isNaN() checks whether a value is Not a Number. It coerces the value to a number first.
console.log(isNaN(10)); // false
console.log(isNaN("10")); // false — "10" → 10
console.log(isNaN("hello")); // true
console.log(isNaN(undefined)); // true — undefined → NaN| Undeclared | Undefined |
|---|---|
| Does not exist in the program | Declared but not assigned a value |
Causes a runtime ReferenceError |
Returns undefined |
var a;
a; // undefined
b; // ReferenceError: b is not definedGlobal variables are accessible throughout the entire codebase without any scope restriction. Avoid unnecessary globals — they use extra memory and can be accidentally overwritten.
JavaScript is both interpreted and compiled — modern JS engines use JIT (Just-In-Time) Compilation.
Parse → Compile → Execute
The JS engine (e.g. V8) parses the code, compiles it to machine code, then executes it — making execution fast.
JavaScript is case-sensitive. age and Age are treated as different identifiers.
let age = 20;
let Age = 30;
console.log(age); // 20
console.log(Age); // 30Despite the name, Java and JavaScript are completely unrelated languages. JavaScript was originally named "Mocha," then "LiveScript," and was renamed to JavaScript as a marketing decision when Java was popular. They differ in syntax, runtime, type system, and use cases.
JavaScript is called single-threaded because it executes one task at a time using a single call stack.
console.log(1);
console.log(2);
console.log(3);
// Output: 1, 2, 3 — always in orderECMAScript is the standard specification that defines how JavaScript should work.
- ECMAScript → Rules / Specification
- JavaScript → Implementation of those rules
ES6 / ES2015 was the most impactful update, introducing: let & const, arrow functions, classes, promises, and template literals.
Primitive types store actual values and are immutable and copied by value.
| Primitive Types |
|---|
| String |
| Number |
| Boolean |
| Undefined |
| Null |
| BigInt |
| Symbol |
Reference types store the memory reference/address of the value and are copied by reference.
| Reference Types |
|---|
| Object |
| Array |
| Function |
let user1 = { name: "Dimitri" };
let user2 = user1;
user2.name = "Alex";
console.log(user1.name); // "Alex" — both point to same memoryUse typeof to detect the type.
// Primitives
typeof "Hello" // "string"
typeof 10 // "number"
typeof true // "boolean"
typeof undefined // "undefined"
typeof 10n // "bigint"
typeof Symbol() // "symbol"
// Non-Primitives
typeof {} // "object"
typeof [] // "object"
typeof function(){}// "function"
// ⚠️ Famous JS quirk:
typeof null // "object" — this is a known bug in JavaScriptisNaN() |
Number.isNaN() |
|---|---|
| Performs type coercion first | No type coercion |
isNaN("hello") → true |
Number.isNaN("hello") → false |
isNaN(null) → false (null→0) |
Number.isNaN(null) → false |
// isNaN coerces:
isNaN(" ") // false — " " → 0
isNaN(null) // false — null → 0
isNaN(undefined) // true — undefined → NaN
// Number.isNaN is strict:
Number.isNaN(NaN) // true
Number.isNaN("hello") // false — no coercion, not actually NaN
Number.isNaN(undefined)// falseLexical scope means a function can access variables from its parent scope based on where it is defined (not where it is called).
let name = "Dimitri";
function outer() {
function inner() {
console.log(name); // "Dimitri" — accesses parent scope
}
inner();
}
outer();JavaScript functions are first-class — they can be stored, passed, and returned like any other value.
// 1. Function as a variable
const greet = function () {
console.log("Hello");
};
// 2. Function as argument
function execute(fn) {
fn();
}
execute(greet);
// 3. Function returning a function
function outer() {
return function () {
console.log("Inner");
};
}
const fn = outer();
fn();A first-order function neither accepts nor returns another function.
function add(a, b) {
return a + b;
}
console.log(add(2, 3)); // 5A higher-order function accepts another function as an argument or returns a function.
// Accepts a function
function execute(fn) {
fn();
}
// Returns a function
function outer() {
return function () {
console.log("Inner");
};
}Built-in examples: map(), filter(), reduce(), forEach().
A unary function accepts only one argument.
function square(num) {
return num * num;
}| Argument Count | Name |
|---|---|
| 1 | Unary |
| 2 | Binary |
| 3 | Ternary |
Currying converts a function with multiple arguments into nested functions that each take one argument at a time.
function add(a) {
return function (b) {
return function (c) {
return a + b + c;
};
};
}
console.log(add(1)(2)(3)); // 6A pure function always returns the same output for the same input and causes no side effects.
// Pure
function add(a, b) {
return a + b;
}
// Impure — modifies external state
let count = 0;
function increase() {
count++;
return count;
}Benefits: Predictable output, easier testing, simpler debugging, and reusability.
An IIFE (Immediately Invoked Function Expression) executes immediately after being created. Commonly used to create private variables and avoid polluting the global scope.
(function () {
console.log("Hello");
})();
// Arrow function IIFE
(() => {
console.log("Hello");
})();The first () makes it an expression; the last () immediately calls it.
Memoization stores function results so repeated inputs return cached results instead of recalculating.
Benefits: Improved performance, avoids redundant calculations.
Used in: Recursion, dynamic programming, React (React.memo, useMemo).
A closure is created when a function remembers variables from its outer scope even after the outer function has finished execution.
function outer() {
let count = 0;
function inner() {
count++;
console.log(count);
}
return inner;
}
const counter = outer();
counter(); // 1
counter(); // 2
counter(); // 3Real uses: Data hiding, private variables, callbacks, memoization, currying.
| Normal Function | Arrow Function |
|---|---|
Has its own this |
Uses lexical this |
| Can be used as constructor | Cannot be used as constructor |
Has arguments object |
No arguments object |
Uses function keyword |
Uses => syntax |
Function declarations are fully hoisted and can be called before their definition.
Function expressions follow variable hoisting rules — const/let expressions land in TDZ.
// Declaration — works before definition
greet();
function greet() {
console.log("Hello");
}
// Expression — ReferenceError if called before
sayHi(); // ❌ ReferenceError
const sayHi = function () {
console.log("Hi");
};var |
let |
|---|---|
| Function scoped | Block scoped |
| Redeclaration allowed | Redeclaration not allowed |
| Can be updated | Can be updated |
Hoisted with undefined |
Hoisted but in TDZ |
| Old way | Modern way (ES6) |
The name let comes from mathematical expressions like let x = 10.
The Temporal Dead Zone (TDZ) is the period where a let or const variable is hoisted but cannot be accessed before initialization.
console.log(a); // undefined — var initializes with undefined
console.log(b); // ReferenceError — b is in TDZ
var a = 10;
let b = 20;TDZ applies only to let and const, not var.
Wrap each case in {} to create separate block scopes and avoid redeclaration errors.
// ❌ Error — same scope
switch (1) {
case 1:
let a = 10;
break;
case 2:
let a = 20; // SyntaxError: already declared
break;
}
// ✅ Correct — separate block scopes
switch (1) {
case 1: {
let a = 10;
break;
}
case 2: {
let a = 20;
break;
}
}Variables declared with let and const are accessible only within the {} block where they are defined.
{
let a = 10;
const b = 20;
console.log(a, b); // 10 20
}
console.log(a); // ReferenceErrorThe scope chain is the process of searching for a variable from the inner scope outward to the global scope.
let a = 10;
function outer() {
let b = 20;
function inner() {
let c = 30;
console.log(a, b, c); // 10 20 30
}
inner();
}
outer();An execution context is the environment where JavaScript code is evaluated and executed. There are three types:
- Global Execution Context (GEC) — created once when the program starts.
- Function Execution Context (FEC) — created each time a function is called.
- Eval Execution Context — created when
eval()is called.
Two phases:
- Memory Creation Phase — variables/functions are allocated in memory.
- Execution Phase — code runs line by line.
When the JS file runs, the GEC is created. During the memory phase, var variables are initialized with undefined, let/const stay in TDZ, and function declarations are stored with their full definitions.
The Memory Creation Phase is the first phase of execution context creation where JavaScript allocates memory before executing code.
console.log(a); // undefined
console.log(b); // ReferenceError (TDZ)
hello(); // "Hello"
var a = 10;
let b = 20;
function hello() {
console.log("Hello");
}| Identifier | During Memory Phase |
|---|---|
a (var) |
undefined |
b (let) |
TDZ (not accessible) |
hello |
Full function stored |
The Call Stack is a LIFO (Last In, First Out) data structure used by the JS engine to manage execution contexts and function calls.
- JS is single-threaded — only one call stack.
- The most recently called function is always at the top.
Synchronous — code executes line by line, blocking until each line completes.
console.log(1);
console.log(2);
console.log(3);
// 1, 2, 3Asynchronous — allows non-blocking operations; the rest of the code continues while waiting.
console.log(1);
setTimeout(() => console.log(2), 1000);
console.log(3);
// 1, 3, 2The Event Loop continuously checks whether the call stack is empty and moves callbacks from the queues to the call stack.
Priority order:
- Call Stack (synchronous code)
- Microtask Queue (Promises)
- Macrotask / Callback Queue (setTimeout, etc.)
The Callback Queue (Macrotask Queue) stores callbacks from asynchronous operations, waiting for the call stack to be empty.
Examples: setTimeout, setInterval, DOM events.
The Microtask Queue is a high-priority queue that stores Promise callbacks and executes them before the callback queue.
Examples: Promise.then, .catch, .finally, queueMicrotask.
Promise.resolve().then(() => {
console.log("Microtask"); // runs before any setTimeout
});Web APIs are browser features that allow JavaScript to perform asynchronous operations outside the call stack.
Examples: setTimeout, fetch, DOM event listeners, geolocation.
setTimeout |
setInterval |
|---|---|
| Runs once | Runs repeatedly |
| Single execution | Continuous execution |
setTimeout(() => console.log("Once"), 1000);
setInterval(() => console.log("Repeat"), 1000);Even with a 0 ms delay, setTimeout first goes to Web APIs, then the Callback Queue, and only executes when the call stack is completely empty.
Call Stack → Web API → Callback Queue → Event Loop → Call Stack
A callback is a function passed into another function to be executed later, usually after completing an asynchronous task.
function fetchData(callback) {
setTimeout(() => {
console.log("Data fetched");
callback();
}, 2000);
}
function processData() {
console.log("Processing data");
}
fetchData(processData);
// Data fetched
// Processing dataCallback inside a callback (nested callbacks) creates Callback Hell — deeply nested, hard-to-maintain code.
setTimeout(() => {
console.log("Step 1");
setTimeout(() => {
console.log("Step 2");
setTimeout(() => {
console.log("Step 3");
}, 1000);
}, 1000);
}, 1000);A Promise is an object that represents either the success or failure of an asynchronous operation in the future.
const promise = new Promise((resolve, reject) => {
let success = true;
if (success) {
resolve("Data fetched");
} else {
reject("Error");
}
});
promise
.then((data) => console.log(data))
.catch((err) => console.log(err));Promise creation is synchronous; .then() / .catch() handling is asynchronous.
console.log(1);
const p = new Promise((resolve) => {
console.log(2); // synchronous — runs immediately
resolve();
});
p.then(() => console.log(3)); // async — microtask
console.log(4);
// Output: 1, 2, 4, 3A Promise can be in exactly one of three states:
| State | Description |
|---|---|
| Pending | Initial state, neither resolved nor rejected |
| Fulfilled | Operation succeeded (resolve called) |
| Rejected | Operation failed (reject called) |
Once a promise settles (fulfilled or rejected), its state cannot change again. Only the first resolve or reject call takes effect.
new Promise((resolve, reject) => {
resolve("First");
resolve("Second"); // ignored
reject("Third"); // ignored
}).then(console.log); // "First"Promise chaining executes multiple async operations sequentially using .then(). Each .then() returns a new Promise.
Promise.resolve(5)
.then((num) => num * 2) // 10
.then((result) => console.log(result)); // 10step1()
.then((result) => {
console.log(result);
return step2(result);
})
.then((result) => {
console.log(result);
return step3(result);
})
.then(console.log)
.catch(console.log);| Method | Behavior |
|---|---|
Promise.all |
Resolves when all succeed; fails on any reject |
Promise.allSettled |
Returns results of all (success + failure) |
Promise.race |
Settles with the first to settle (resolve or reject) |
Promise.any |
Settles with the first to succeed |
// Promise.all
Promise.all([Promise.resolve(1), Promise.resolve(2)])
.then(console.log); // [1, 2]
// Promise.race — first to settle wins
Promise.race([
new Promise(r => setTimeout(() => r("Slow"), 1000)),
new Promise(r => setTimeout(() => r("Fast"), 100))
]).then(console.log); // "Fast"async/await is syntactic sugar over promises that makes async code look synchronous and easier to read.
async function getData() {
const result = await Promise.resolve("Hello");
console.log(result); // "Hello"
}- An
asyncfunction always returns a Promise, even for non-Promise values. awaitpauses the async function until the Promise settles.
async function test() {
console.log(1);
await Promise.resolve();
console.log(2); // runs after current synchronous code
}
test();
console.log(3);
// Output: 1, 3, 2Pros of Promises over Callbacks:
- Better readability (no pyramid of doom)
- Cleaner error handling (
.catch()) - Supports chaining
- Works with
async/await
Cons of Promises:
- Slightly steeper learning curve
- Cannot be easily cancelled
- Requires understanding of the microtask queue
this refers to the object currently executing the function. Its value depends on how the function is called.
| Context | this value |
|---|---|
| Global scope (browser) | window |
| Normal function (non-strict) | window |
| Normal function (strict mode) | undefined |
| Arrow function | Lexical this from enclosing scope |
| Object method | The object itself |
| Event listener | The DOM element |
const obj = {
name: "Dimitri",
greet() {
console.log(this.name); // "Dimitri"
}
};
obj.greet();call() — immediately invokes a function with a specific this value.
function greet() {
console.log(this.name);
}
greet.call({ name: "Dimitri" }); // "Dimitri"apply() — same as call(), but passes arguments as an array.
function add(a, b) {
console.log(a + b);
}
add.apply(null, [2, 3]); // 5bind() — returns a new function with a fixed this (does not call immediately).
const person = { name: "Rahul" };
function greet() {
console.log("Hello " + this.name);
}
const boundGreet = greet.bind(person);
boundGreet(); // "Hello Rahul"const user = { name: "Dimitri", age: 22 };
Object.keys(user); // ["name", "age"]
Object.values(user); // ["Dimitri", 22]
Object.entries(user); // [["name", "Dimitri"], ["age", 22]]
// Looping with entries:
for (const [key, value] of Object.entries(user)) {
console.log(`${key}: ${value}`);
}Object.freeze() |
Object.seal() |
|---|---|
| Cannot add / delete / update | Cannot add / delete |
| Fully immutable | Existing properties can update |
const user = { name: "Rahul", age: 22 };
Object.freeze(user);
user.name = "Aman"; // ignored
user.city = "Delhi"; // ignored
Object.seal(user);
user.age = 25; // ✅ allowed
user.city = "Delhi"; // ❌ ignoredNote: Both
freezeandsealare shallow — nested objects remain mutable.
Optional chaining (?.) safely accesses nested properties without throwing an error if a property doesn't exist.
const user = {};
console.log(user.address?.city); // undefined (no error)
const student = { marks: { math: 90 } };
console.log(student.marks?.math); // 90
console.log(student.address?.city); // undefined?? returns the right-hand value only when the left-hand value is null or undefined (unlike || which triggers on all falsy values).
console.log(null ?? "Default"); // "Default"
console.log(undefined ?? "Default"); // "Default"
console.log(0 ?? "Default"); // 0 — 0 is not null/undefined
console.log("" ?? "Default"); // "" — empty string is not null/undefinedPrototypal inheritance is a mechanism where objects can inherit properties and methods from another object through the prototype chain.
const animal = {
eats: true,
walk() { console.log("Animal walks"); }
};
const dog = {
bark() { console.log("Dog barks"); }
};
dog.__proto__ = animal; // dog inherits from animal
console.log(dog.eats); // true
dog.walk(); // "Animal walks"
dog.bark(); // "Dog barks"Using constructors:
function Person(name) {
this.name = name;
}
Person.prototype.sayHi = function () {
console.log(`Hi ${this.name}`);
};
const user = new Person("Dimitri");
user.sayHi(); // "Hi Dimitri"
// Chain: user → Person.prototype → Object.prototype → null__proto__ vs prototype:
__proto__ |
prototype |
|---|---|
| Actual prototype link of an object | Property on constructor functions |
| Lives on object instances | Lives on function objects |
| Used for property lookup | Used when creating objects with new |
The prototype chain is the mechanism by which JavaScript traverses through linked prototype objects to look up properties and methods. The chain ends at Object.prototype.__proto__ === null.
// dog → animal → Object.prototype → null
console.log(dog.eats); // found at animal in the chainGarbage collection is an automatic memory management process that removes unreachable objects from memory. JavaScript uses a mark-and-sweep algorithm — objects that can no longer be accessed are cleaned up automatically.
Events are actions or occurrences in the browser that JavaScript can detect and respond to.
| Event | Description |
|---|---|
click |
User clicked |
input |
User typed |
submit |
Form submitted |
keydown |
Key pressed |
mouseover |
Mouse hovered |
load |
Page loaded |
const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
console.log("Button clicked");
});addEventListener() is the recommended approach — it supports multiple listeners and better event management.
Event Flow is the complete path an event travels through the DOM in three phases:
Capturing Phase → Target Phase → Bubbling Phase
Event Propagation is the actual movement of an event through the DOM during these phases.
Event Capturing is the phase where an event travels from the top of the DOM tree down to the target element.
parent.addEventListener("click", () => {
console.log("Parent Capture");
}, true); // true = capturing phase
child.addEventListener("click", () => {
console.log("Child");
});
// Click on child → "Parent Capture", "Child"Pass true as the third argument to addEventListener to listen in the capturing phase (default is false = bubbling).
Event Bubbling is the default behavior where an event starts at the target element and bubbles up to parent elements.
parent.addEventListener("click", () => console.log("Parent"));
child.addEventListener("click", () => console.log("Child"));
// Click on child → "Child", "Parent"Event Delegation attaches a single event listener to a parent element to handle events for its children, using event bubbling.
// Instead of multiple listeners:
// btn1.addEventListener(...), btn2.addEventListener(...)
// Use one parent listener:
const parent = document.getElementById("parent");
parent.addEventListener("click", (event) => {
console.log(event.target.innerText); // identifies which button was clicked
});Benefits: Fewer listeners, works for dynamically added elements.
Template literals use backticks and allow embedded expressions with ${}.
const name = "Dimitri";
console.log(`Hello ${name}`); // "Hello Dimitri"Debouncing delays function execution until the user stops triggering events for a specified duration. The timer resets on every new event.
function debounce(fn, delay) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}Used in: Search inputs (delay API calls), resize events.
Throttling ensures a function executes at most once in a specified time interval, even if the event fires repeatedly.
Used in: Scroll events, window resize, button spam prevention.
Generators are special functions that can pause and resume execution using yield.
function* gen() {
yield 1;
yield 2;
yield 3;
}
const g = gen();
console.log(g.next().value); // 1
console.log(g.next().value); // 2Falsy values (only these 8 evaluate to false):
false, 0, -0, 0n, "", null, undefined, NaN
Truthy values — everything else, including:
true, 1, -5, "hello", "0",
[], // empty array ← truthy!
{}, // empty object ← truthy!
function(){}&& (AND) — stops at the first falsy value and returns it.
console.log(0 && "Hello"); // 0
console.log(true && "JS" && 0 && "Done"); // 0|| (OR) — stops at the first truthy value and returns it.
console.log("" || "Default"); // "Default"
console.log(false || 0 || null || "Hello"); // "Hello"localStorage — persists data with no expiration.
sessionStorage — data is cleared when the browser tab closes.
Cookies — small data files stored in the browser, mainly used for authentication, session management, and tracking.
| Feature | Cookie | localStorage | sessionStorage |
|---|---|---|---|
| Expiry | Manual / set | Never | Tab close |
| Size limit | ~4KB | ~5-10MB | ~5-10MB |
| Sent to server | Yes | No | No |
| Use case | Auth/sessions | Persistent data | Temporary data |
| Method | Description | Example |
|---|---|---|
push() |
Adds element(s) to the end | [1,2].push(3) → [1,2,3] |
pop() |
Removes last element | [1,2,3].pop() → [1,2] |
unshift() |
Adds element(s) to the start | [1,2].unshift(0) → [0,1,2] |
shift() |
Removes first element | [1,2,3].shift() → [2,3] |
forEach() |
Loops over array (returns nothing) | [1,2,3].forEach(n => console.log(n)) |
map() |
Returns new array with transformed elements | [1,2,3].map(n => n * 2) → [2,4,6] |
filter() |
Returns elements matching a condition | [1,2,3,4].filter(n => n % 2 === 0) → [2,4] |
reduce() |
Reduces array to a single value | [1,2,3].reduce((acc,n) => acc+n, 0) → 6 |
includes() |
Checks if value exists | [1,2,3].includes(2) → true |
indexOf() |
Returns index of first match | [1,2,3].indexOf(2) → 1 |
find() |
Returns first matching element | [1,2,3,4].find(n => n > 2) → 3 |
slice() |
Returns a copy of a portion (non-destructive) | [1,2,3,4].slice(1,3) → [2,3] |
splice() |
Inserts/removes elements in place (mutates) | arr.splice(2,1,"X") — removes 1, adds "X" |
concat() |
Merges arrays | [1,2].concat([3,4]) → [1,2,3,4] |
join() |
Joins elements into a string | [1,2,3].join("-") → "1-2-3" |
slicevssplice:sliceis non-destructive (returns a copy);splicemutates the original array.
Q1
console.log('Start');
const promise = new Promise((resolve) => {
console.log('Promise executor');
resolve('Resolved');
});
promise.then(console.log);
console.log('End');Output:
Start→Promise executor→End→Resolved
Q2
Promise.resolve(1)
.then(v => { console.log(v); return v + 1; }) // 1
.then(v => { console.log(v); }) // 2
.then(v => { console.log(v); return Promise.resolve(3); }) // undefined
.then(console.log); // 3Output:
1→2→undefined→3
Q3
Promise.resolve(Promise.resolve(Promise.resolve(1)))
.then(console.log);Output:
1
Promise.resolve()automatically unwraps nested promises recursively.
Q4
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');Output:
1→4→3→2
Microtasks (Promise) run before macrotasks (setTimeout).
Q5 — Error in chain
Promise.resolve('Start')
.then(v => { console.log(v); throw new Error('Oops!'); })
.then(() => console.log('Hello')) // skipped
.catch(err => { console.log('Caught:', err.message); return 'Recovered'; })
.then(console.log);Output:
Start→Caught: Oops!→Recovered
Q6 — Only first settle counts
new Promise((resolve, reject) => {
resolve('First');
resolve('Second'); // ignored
reject('Third'); // ignored
}).then(console.log);Output:
First
Q7 — Undefined return in chain
new Promise(resolve => resolve(1))
.then(v => { console.log(v); return 2; })
.then(v => { console.log(v); }) // no return
.then(v => { console.log(v); return Promise.reject('Error'); })
.catch(err => { console.log(err); })
.then(() => console.log('Done'));Output:
1→2→undefined→Error→Done
Q8 — async/await
async function test() {
console.log('1');
await Promise.resolve();
console.log('2');
}
console.log('3');
test();
console.log('4');Output:
3→1→4→2
Q9 — Promise.all fails fast
Promise.all([Promise.resolve(1), Promise.reject('Error'), Promise.resolve(3)])
.then(v => console.log('Success:', v))
.catch(err => console.log('Failed:', err));Output:
Failed: Error
Q10 — Nested promise microtask order
Promise.resolve(1)
.then(v => {
console.log(v);
Promise.resolve(2).then(console.log);
return 3;
})
.then(console.log);Output:
1→3→2
The nested promise creates a separate microtask entry; the outer chain resolves first.
Q11 — Promise.race
Promise.race([
new Promise(r => setTimeout(() => r("Slow"), 1000)),
new Promise(r => setTimeout(() => r("Fast"), 100)),
new Promise((_, rej) => setTimeout(() => rej("Error"), 50)),
]).then(console.log).catch(console.log);Output:
Error(rejects at 50ms — fastest to settle)
Q12 — Multiple catch/then
Promise.reject('First error')
.catch(e => { console.log('Catch 1:', e); throw new Error('Second error'); })
.catch(e => { console.log('Catch 2:', e.message); return 'Recovered'; })
.then(v => { console.log('Then:', v); throw new Error('Third error'); })
.catch(e => console.log('Catch 3:', e.message));Output:
Catch 1: First error→Catch 2: Second error→Then: Recovered→Catch 3: Third error
Q13 — async functions return promises
async function func1() { return 1; }
async function func2() { return Promise.resolve(2); }
func1().then(console.log);
func2().then(console.log);
console.log(3);Output:
3→1→2
Q14 — finally
Promise.resolve('Success')
.finally(() => { console.log('Finally 1'); return 'ignored'; })
.then(v => console.log('Then 1:', v))
.finally(() => { console.log('Finally 2'); throw new Error('Finally error'); })
.then(v => console.log('Then 2:', v)) // skipped
.catch(e => console.log('Catch:', e.message));Output:
Finally 1→Then 1: Success→Finally 2→Catch: Finally error
finallydoes not change the resolved value but can throw errors that break the chain.
Q15 — Macro & Micro interleaving
console.log('Start');
setTimeout(() => {
console.log('Timeout 1');
Promise.resolve().then(() => console.log('Promise in Timeout'));
}, 0);
Promise.resolve()
.then(() => {
console.log('Promise 1');
setTimeout(() => console.log('Timeout in Promise'), 0);
})
.then(() => console.log('Promise 2'));
setTimeout(() => console.log('Timeout 2'), 0);
console.log('End');Output:
Start→End→Promise 1→Promise 2→Timeout 1→Promise in Timeout→Timeout 2→Timeout in Promise
Q16 — Promise pending, .then queued only on resolve
// .then() only enters microtask queue when the promise is resolved/rejected.
// If the promise is still pending, the callback is stored internally — not queued yet.
const promise = new Promise((resolve) => {
console.log('Executor start');
setTimeout(() => { console.log('Timeout in executor'); resolve('Done'); }, 0);
console.log('Executor end');
});
promise.then(v => console.log('Then:', v));
console.log('After promise creation');Output:
Executor start→Executor end→After promise creation→Timeout in executor→Then: Done
Q17 — Promise with reject and chaining
console.log(1);
setTimeout(() => console.log(2), 10);
setTimeout(() => console.log(3), 0);
new Promise((_, reject) => {
console.log(4);
reject(5);
console.log(6);
})
.then(() => console.log(7)) // skipped (rejected)
.catch(() => console.log(8)) // handles reject
.then(() => console.log(9)) // continues
.catch(() => console.log(10)) // skipped
.then(() => console.log(11))
.then(console.log) // undefined
.finally(() => console.log(12));
console.log(13);Output:
1→4→6→13→8→9→11→undefined→12→3→2
Q18
console.log(1);
setTimeout(() => {
console.log(3);
Promise.resolve().then(() => console.log(4));
}, 0);
Promise.resolve().then(() => {
console.log(5);
setTimeout(() => console.log(7), 0);
});
console.log(6);Output:
1→6→5→3→4→7
Q19
console.log('A');
setTimeout(() => {
console.log('B');
Promise.resolve().then(() => {
console.log('C');
setTimeout(() => console.log('D'), 0);
});
}, 0);
Promise.resolve().then(() => {
console.log('E');
setTimeout(() => {
console.log('F');
Promise.resolve().then(() => console.log('G'));
}, 0);
});
setTimeout(() => console.log('H'), 0);
console.log('I');Output:
A→I→E→B→C→H→F→G→D
Q20
setTimeout(() => {
console.log(2);
Promise.resolve().then(() => console.log(3)).then(() => console.log(4));
}, 0);
setTimeout(() => {
console.log(5);
Promise.resolve().then(() => console.log(6));
}, 0);
Promise.resolve()
.then(() => { console.log(7); setTimeout(() => console.log(8), 0); })
.then(() => console.log(9));
console.log(10);Output:
10→7→9→2→3→4→5→6→8
Q21
setTimeout(() => {
console.log('Timeout 1');
Promise.resolve()
.then(() => {
console.log('Promise 1');
setTimeout(() => {
console.log('Timeout 2');
Promise.resolve().then(() => console.log('Promise 2'));
}, 0);
})
.then(() => console.log('Promise 3'));
}, 0);
console.log('Start');Output:
Start→Timeout 1→Promise 1→Promise 3→Timeout 2→Promise 2
Q22
console.log('Start');
Promise.resolve().then(() => { console.log('P1'); setTimeout(() => console.log('T1'), 0); });
setTimeout(() => { console.log('T2'); Promise.resolve().then(() => console.log('P2')); }, 0);
Promise.resolve().then(() => { console.log('P3'); setTimeout(() => console.log('T3'), 0); });
setTimeout(() => { console.log('T4'); Promise.resolve().then(() => console.log('P4')); }, 0);
console.log('End');Output:
Start→End→P1→P3→T2→P2→T4→P4→T1→T3
Q23
console.log(1);
setTimeout(() => {
console.log(2);
Promise.resolve().then(() => {
console.log(3);
setTimeout(() => { console.log(4); Promise.resolve().then(() => console.log(5)); }, 0);
});
setTimeout(() => console.log(6), 0);
}, 0);
Promise.resolve().then(() => {
console.log(7);
setTimeout(() => { console.log(8); Promise.resolve().then(() => console.log(9)); }, 0);
});
console.log(10);Output:
1→10→7→2→3→8→9→6→4→5