Predict the output of a JavaScript snippet, choose an answer and review the result. Use these exercises alongside the interview tracks.
Hoisting
getName();
console.log(X);
function getName() {
console.log('Hello');
}
console.log(b);
let b = 20;
console.log(b);
Review the expected output
Hello
Uncaught ReferenceError: X is not defined
The function getName() is hoisted, so calling it prints "Hello". However, X is not defined anywhere, leading to a ReferenceError. The variable b is declared using let, which is hoisted but remains in the Temporal Dead Zone (TDZ) until initialized. Accessing b before initialization results in another ReferenceError, but after initialization, console.log(b); prints 20.
Hoisting
function test() {
if (false) {
var x = 50;
}
console.log(x);
console.log(y);
var y = 100;
console.log(y);
}
test();
Review the expected output
undefined
undefined
100
The variable x is declared inside the if block with var, so it is hoisted to the top of the function but remains undefined since the block doesn't execute. This leads to console.log(x); printing undefined.
The variable y is also hoisted, but only its declaration, not its assignment. So console.log(y); before assignment prints undefined. After y = 100;, console.log(y); prints 100.
Since x and y are declared with var, they are function-scoped and exist throughout the function execution.
Hoisting
var foo = function () {
console.log('I am a function expression!');
};
function foo() {
console.log('I am a function declaration!');
}
foo();
Review the expected output
I am a function expression!
<p>The function declaration <code>function foo()</code> is hoisted first, but it is later overwritten by the function expression assigned to <code>var foo</code>.</p>
<p>When <code>foo()</code> is called, it executes the function expression, printing:</p>
<pre>I am a function expression!</pre>
<p>This happens because variable assignments override function declarations when they share the same name.</p>
Hoisting
if (true) {
console.log(innerVar);
var innerVar = 'I am hoisted!';
console.log(innerVar);
}
console.log(innerVar);
Review the expected output
undefined
I am hoisted!
<p>The variable <code>innerVar</code> is declared with <code>var</code>, which means it is hoisted to the top of its function or global scope.</p>
<p>However, only the declaration is hoisted, not the initialization. This means when <code>console.log(innerVar)</code> is executed before the assignment, it prints <code>undefined</code>.</p>
<p>After <code>innerVar = "I am hoisted!"</code>, the second <code>console.log(innerVar)</code> prints:</p>
<pre>I am hoisted!</pre>
<p>Since <code>var</code> is function-scoped, <code>innerVar</code> is accessible outside the block as well, so the final <code>console.log(innerVar)</code> also prints:</p>
<pre>I am hoisted!</pre>
Hoisting
var x = 10;
console.log(x);
if (true) {
(function () {
var x = 20;
console.log(x);
})();
}
console.log(x);
Review the expected output
10
20
10
<p>The variable <code>x</code> is first declared globally with the value <code>10</code>, so <code>console.log(x)</code> prints:</p>
<pre>10</pre>
<p>Inside the <code>if</code> block, an **immediately invoked function expression (IIFE)** creates a new function scope. A new <code>x</code> is declared inside this function with the value <code>20</code>. This <code>x</code> is separate from the global <code>x</code>, so <code>console.log(x)</code> inside the function prints:</p>
<pre>20</pre>
<p>After the IIFE executes, we return to the global scope, where <code>x</code> is still <code>10</code>. So the final <code>console.log(x)</code> prints:</p>
<pre>10</pre>
<p>This demonstrates **function scope isolation**, where variables declared with <code>var</code> inside a function do not affect the outer scope.</p>
Hoisting
var x = 10;
function test() {
if (x > 20) {
var x = 50;
}
console.log(x);
}
test();
Review the expected output
undefined
<p>The global variable <code>x</code> is declared and initialized with <code>10</code>.</p>
<p>Inside the <code>test()</code> function, a new variable <code>x</code> is declared using <code>var</code>. Due to **hoisting**, this function-scoped <code>x</code> is moved to the top of the function and initialized as <code>undefined</code>.</p>
<p>Since the condition <code>x > 20</code> is false (because hoisted <code>x</code> is <code>undefined</code>), the assignment <code>x = 50</code> never executes.</p>
<p>Thus, when <code>console.log(x)</code> runs inside the function, it prints:</p>
<pre>undefined</pre>
<p>This happens because the local <code>x</code> shadows the global <code>x</code>, and due to hoisting, it is treated as <code>var x;</code> at the start of the function.</p>
Hoisting
function test() {
function foo() {}
var bar;
foo();
bar();
function foo() {
console.log('foo');
}
bar = function () {
console.log('bar');
};
}
test();
Review the expected output
foo
Uncaught TypeError: bar is not a function
<p>Function declarations are fully hoisted, while <code>var</code> variables are hoisted but remain <code>undefined</code> until assigned.</p>
<p><code>foo()</code> runs successfully because function declarations are hoisted with their definitions, printing:</p>
<pre>foo</pre>
<p><code>bar()</code> causes an error because <code>bar</code> is hoisted as <code>undefined</code>, and the function is assigned later:</p>
<pre>Uncaught TypeError: bar is not a function</pre>
Hoisting
function test() {
if (false) {
let x = 50;
}
console.log(x);
console.log(y);
let y = 100;
console.log(y);
}
test();
Review the expected output
Uncaught ReferenceError: x is not defined
<p><code>let</code> variables are hoisted but stay in the <strong>Temporal Dead Zone (TDZ)</strong> until initialized.</p>
<p><code>x</code> is inside a blocked <code>if (false)</code>, so it doesn't exist in scope. <code>console.log(x)</code> throws:</p>
<pre>Uncaught ReferenceError: x is not defined</pre>
<p><code>y</code> is hoisted but still in the TDZ when accessed, causing the same error. After initialization, <code>console.log(y)</code> prints:</p>
<pre>100</pre>
Hoisting
console.log(greet);
var greet = 'Hello';
console.log(greet);
Review the expected output
undefined
Hello
<p>With <code>var</code>, variable declarations are hoisted and initialized to <code>undefined</code> during the creation phase. Therefore, the first <code>console.log(greet)</code> prints <code>undefined</code>. After the assignment, the second prints <code>Hello</code>.</p>
Hoisting
var employeeId = 'abc123';
function foo() {
employeeId();
return;
function employeeId() {
console.log(typeof employeeId);
}
}
foo();
Review the expected output
function
<p>Function declarations are hoisted before variables, so inside <code>foo()</code>, <code>employeeId</code> refers to the function, not the global variable.</p>
<p>Calling <code>employeeId()</code> works since it’s a function, but it prints:</p>
<pre>function</pre>
Hoisting
function foo() {
let a = (b = 0);
a++;
return a;
}
foo();
console.log(typeof a);
console.log(typeof b);
Review the expected output
undefined
number
<p><code>let a = b = 0;</code> is interpreted as:</p>
<pre>let a = 0; b = 0;</pre>
<p>Since <code>b</code> is not declared with <code>let</code> or <code>var</code>, it becomes a **global variable**.</p>
<p><code>a</code> is block-scoped inside <code>foo()</code>, so <code>console.log(typeof a)</code> prints:</p>
<pre>undefined</pre>
<p><code>b</code> exists globally, so <code>console.log(typeof b)</code> prints:</p>
<pre>number</pre>
Hoisting
console.log(typeof foo);
var foo = function () {
console.log('Hello!');
};
Review the expected output
"undefined"
<p>Variable <code>foo</code> is declared with <code>var</code>, so it is hoisted, but only the declaration, not the assignment.</p>
<p>Before the function expression is assigned, <code>typeof foo</code> evaluates to:</p>
<pre>"undefined"</pre>
<p>If it were a function declaration instead of an expression, <code>typeof foo</code> would return <code>"function"</code>.</p>
Hoisting
console.log(a);
let a = 5;
Review the expected output
Uncaught ReferenceError: Cannot access 'a' before initialization
<p>Variables declared with <code>let</code> are hoisted but remain in the **Temporal Dead Zone (TDZ)** until they are initialized.</p>
<p>Since <code>a</code> is accessed before its initialization, it throws:</p>
<pre>Uncaught ReferenceError: Cannot access 'a' before initialization</pre>
Hoisting
var x = 1;
function test() {
console.log(x);
var x = 2;
}
test();
Review the expected output
undefined
<p>Due to **hoisting**, the local <code>var x</code> declaration inside <code>test()</code> is moved to the top.</p>
<p>Thus, inside <code>test()</code>, <code>x</code> is hoisted as <code>var x;</code>, making it <code>undefined</code> at the time of <code>console.log(x)</code>.</p>
<p>Even though there's a global <code>x = 1</code>, the local <code>x</code> shadows it, so the output is:</p>
<pre>undefined</pre>
Hoisting
console.log(foo());
function foo() {
return 'Hello';
}
var foo = function () {
return 'Hi';
};
Review the expected output
"Hello"
<p>Function declarations are hoisted before variable assignments.</p>
<p>So, <code>function foo() { return "Hello"; }</code> is fully hoisted and available at runtime.</p>
<p>The <code>var foo</code> declaration is also hoisted, but the function expression is assigned later, so it doesn't override the function declaration at the time of execution.</p>
<p>Thus, <code>console.log(foo())</code> prints:</p>
<pre>"Hello"</pre>
Hoisting
(function () {
console.log(x);
var x = 10;
})();
Review the expected output
undefined
<p>The variable <code>x</code> is declared with <code>var</code> inside the IIFE (Immediately Invoked Function Expression).</p>
<p>Due to **hoisting**, only the declaration is moved to the top, making <code>x</code> <code>undefined</code> before assignment.</p>
<p>Thus, <code>console.log(x)</code> prints:</p>
<pre>undefined</pre>
Hoisting
console.log(foo);
var foo = 10;
function foo() {}
console.log(foo);
Review the expected output
[Function: foo]
10
<p>The function declaration <code>function foo() {}</code> is hoisted first, then the <code>var foo</code> declaration.</p>
<p>Initially, <code>foo</code> is a function, so <code>console.log(foo)</code> prints:</p>
<pre>[Function: foo]</pre>
<p>Later, <code>foo</code> is reassigned to <code>10</code>, so the second <code>console.log(foo)</code> prints:</p>
<pre>10</pre>
Hoisting
console.log(bar);
function bar() {
console.log('I am bar!');
}
var bar = 5;
console.log(bar);
Review the expected output
[Function: bar]
5
<p>The function declaration <code>bar()</code> is hoisted first.</p>
<p>The <code>var bar</code> declaration is hoisted but not its assignment.</p>
<p>So, initially, <code>console.log(bar)</code> prints the function:</p>
<pre>[Function: bar]</pre>
<p>After <code>bar = 5</code>, the second <code>console.log(bar)</code> prints:</p>
<pre>5</pre>
Hoisting
foo();
var foo = function () {
console.log('Hello!');
};
Review the expected output
Uncaught TypeError: foo is not a function
<p>Function expressions are not hoisted like function declarations.</p>
<p><code>var foo</code> is hoisted as <code>undefined</code>, so calling <code>foo()</code> before assignment throws:</p>
<pre>Uncaught TypeError: foo is not a function</pre>
Hoisting
console.log(a);
let a;
console.log(a);
Review the expected output
Uncaught ReferenceError: Cannot access 'a' before initialization
<p>The <code>let</code> variable <code>a</code> is hoisted but remains in the **Temporal Dead Zone (TDZ)** until it is initialized.</p>
<p>Accessing it before initialization throws:</p>
<pre>Uncaught ReferenceError: Cannot access 'a' before initialization</pre>
Hoisting
console.log(x);
var x;
console.log(x);
Review the expected output
undefined
undefined
<p>The variable <code>x</code> is declared with <code>var</code>, so it is hoisted with an initial value of <code>undefined</code>.</p>
<p>Thus, both <code>console.log(x)</code> statements print:</p>
<pre>undefined</pre>
Hoisting
var a = 10;
console.log(a);
function fn() {
console.log(a);
var a = 20;
a++;
console.log(a);
if (a) {
var a = 30;
a++;
console.log(a);
}
console.log(a);
}
fn();
console.log(a);
Review the expected output
10
undefined
21
31
31
10
<p>Global <code>a = 10</code>, so first <code>console.log(a)</code> prints:</p>
<pre>10</pre>
<p>Inside <code>fn()</code>, <code>var a</code> is hoisted as <code>undefined</code>, so:</p>
<pre>undefined</pre>
<p>After assignments:</p>
<pre>21</pre>
<pre>31</pre>
<pre>31</pre>
<p>Global <code>a</code> remains unchanged:</p>
<pre>10</pre>
<p><strong>Takeaway:</strong> <code>var</code> is function-scoped, causing shadowing.</p>
When a method using an arrow function accesses `this`, it lexically captures the instance scope, ensuring `this` correctly points to the class instance even when passed as a standalone callback.
<ul>
<li>Synchronous code runs first (the console.log("Start") and console.log("End")).</li>
<li>Promise.resolve() creates a resolved Promise, but it is asynchronous. The .then() handler associated with it will not be executed immediately. Instead, it will be queued in the microtask queue.</li>
<li> The Promise.then() callback runs after all synchronous code finishes, so "Promise" is logged after "Start" and "End".</li>
</ul>
Promise
const p = new Promise((resolve, reject) => {
let success = true;
success ? resolve('Success') : reject('Failed');
});
p.then(console.log).catch(console.error);
Review the expected output
Success
Private class fields prefixed with `#` cannot be accessed outside the declaring class body. Attempting to read them throws a SyntaxError at compile/parse time.
Promise
Promise.resolve(10)
.then((num) => num * 2)
.then((num) => num + 5)
.then(console.log);
Review the expected output
25
The subclass constructor must call `super()` before accessing `this`. Once initialized, methods inherited from the prototype chain resolve correctly.
Promise
function doubleAfter2Sec(x) {
return new Promise((resolve) => {
setTimeout(() => resolve(x * 2), 2000);
});
}
Promise.resolve(10).then(doubleAfter2Sec).then(console.log); // 20 after 2 seconds
Review the expected output
20
Static methods and properties belong to the class constructor function itself rather than instances. Accessing static members via an instance yields `undefined`.
Lexical closure captures the variable reference, not just the initial value. Functions returned from higher-order functions retain access to outer scoped variables.
Generator functions yield values on each `.next()` call. The returned iterator object has `value` and `done` properties reflecting execution state.
Prototype
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function () {
console.log('Hi, I am ' + this.name);
};
const p = new Person('Chetan');
p.sayHello();
Review the expected output
Hi, I am Chetan
The Temporal Dead Zone (TDZ) prevents accessing `let` and `const` variables before their declaration line is executed, throwing a ReferenceError.
Prototype
function Animal() {}
Animal.prototype.sound = 'Roar';
const tiger = new Animal();
console.log(tiger.sound);
Review the expected output
Roar
Function declarations are hoisted with their definitions, allowing them to be called before declaration. Variable declarations assigned function expressions remain undefined until executed.
Prototype
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function () {
console.log('undefined is eating');
};
function Dog(name) {
Animal.call(this, name); // Call super constructor
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function () {
console.log('undefined says woof');
};
const dog = new Dog('Bruno');
dog.eat();
dog.bark();
Review the expected output
Bruno is eating
Bruno says woof
In non-strict mode, calling a regular function without an explicit context sets `this` to the global object (or undefined in strict mode).
Destructuring with default values assigns the fallback only when the target property is strictly `undefined`, not when it is `null` or falsey.
Prototype
function Gadget() {}
Gadget.prototype.version = '1.0';
const g = new Gadget();
g.version = '2.0';
console.log(g.version);
console.log(g.__proto__.version);
Review the expected output
2.0
1.0
Array.prototype.reduce accumulates values through an accumulator callback, returning the aggregated result across all iterations.
Class
class Test {
constructor() {
this.value = 42;
}
static getValue() {
return this.value;
}
}
Test.value = 100;
console.log(Test.getValue());
Review the expected output
100
Because this inside a static method refers to the class itself, and Test.value is 100.
Class
class Parent {
constructor() {
this.name = 'Parent';
}
sayHi() {
console.log('Hi from ' + this.name);
}
}
class Child extends Parent {
constructor() {
super(parenName);
this.name = 'Child';
}
sayHi() {
super.sayHi();
}
}
const c = new Child();
c.sayHi();
Review the expected output
Hi from Child
The spread operator performs a shallow copy of properties. Nested objects retain reference equality with the source object.
Class
class Parent {
constructor(name) {
this.parentName = name;
}
sayHi() {
console.log('Hi from ' + this.parentName);
}
}
class Child extends Parent {
constructor(parenName, childName) {
super();
this.name = childName;
}
sayHi() {
super.sayHi();
}
}
const c = new Child('aaa', 'bbb');
c.sayHi();
Review the expected output
Hi from aaa
Promise microtasks execute immediately after the current synchronous execution context empties, before any macrotasks (setTimeout) execute.
Class
class Foo {
constructor() {
this.count++;
}
}
const a = new Foo();
const b = new Foo();
console.log(a.count);
console.log(b.count);
Review the expected output
NaN
NaN
async functions always return a Promise. A bare return value is implicitly wrapped with `Promise.resolve(value)`.
Class
class Logger {
log = () => {
console.log(this.message);
};
constructor(msg) {
this.message = msg;
}
}
const l = new Logger('Hello');
const logFn = l.log;
logFn();
Review the expected output
Hello
Object.freeze creates a shallow-immutable object, preventing property additions, deletions, or modifications.
Class
class Secret {
#data = 'hidden';
reveal() {
return this.#data;
}
}
const s = new Secret();
console.log(s.reveal());
console.log(s.#data);
Review the expected output
hidden
throws SyntaxError
Curried functions take arguments one at a time via nested unary functions, returning intermediate closures until all arguments are satisfied.
Class
class Magic {
static count = 0;
constructor() {
Magic.count++;
}
}
const m1 = new Magic();
const m2 = new Magic();
console.log(m1.count);
console.log(Magic.count);
Review the expected output
undefined
2
undefined as static property on the class itself, not on the instances.2, because we created two instances and each one increased the static count.
Class
const obj = new Test(); // ❓
class Test {
constructor() {
console.log('Created!');
}
}
Review the expected output
Cannot access 'Test' before initialization"
Classes are not hoisted, unlike function declarations.
var-let-const
console.log(a); // undefined (hoisted)
var a = 10;
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 20;
Review the expected output
undefined
Cannot access 'b' before initialization
The event loop drains the entire microtask queue before executing the next macrotask from the task queue.
var-let-const
if (true) {
var x = 'I am var';
let y = 'I am let';
const z = 'I am const';
}
console.log(x);
console.log(y);
console.log(z);
Review the expected output
I am var
y is not defined
as let and const are block scope so not accessibal after { }
var-let-const
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log('var loop:', i), 1000);
}
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log('let loop:', j), 1000);
}
Review the expected output
var loop: 3
var loop: 3
var loop: 3
let loop: 1
let loop: 2
let loop: 3
as var has function or gloabal scope so after there is only single memory assign to var one so it prints last value and in case of let for ecah {} it will maintain different value
var-let-const
for (var i = 0; i < 3; i++) {
(function () {
console.log(i);
})();
}
Review the expected output
0
1
2
as var is fun or global scope for inside intertaion we have fun so for each iteration we have seperate bscope with diff value
var-let-const
for (var i = 0; i < 3; i++) {
(function () {
setTimeout(() => {
console.log(i);
}, 10000);
})();
}
Review the expected output
3
3
3
as i is in outter scope and we are just accesing it so latest value only
var-let-const
function printFor() {
for (var i = 0; i < 3; i++) {
setTimeout(() => {
printNo(i);
}, 1000);
}
}
function printNo(num) {
console.log(num);
}
printFor();
Review the expected output
3
3
3
Template literals evaluate embedded expressions inside `${}` using their toString representations.
var-let-const
function printFor() {
for (var i = 0; i < 3; i++) {
function printNo(num) {
setTimeout(() => {
console.log(num);
}, 1000);
}
printNo(i);
}
}
printFor();
Review the expected output
0
1
2
here becaz of closure every param we pass will be lock with that instance of fun so diff value
npw in global scope we have count variable so 2++ => 3
Arrow_Function
var name = 'Angular';
var person = {
name: 'React',
age: 30,
getDetail: function () {
console.log('getDetail ' + this.name);
},
getName: () => {
console.log('getName ' + this.name);
},
};
person.getDetail();
person.getName();
Review the expected output
getDetail React
getName Angular
now getDetail is normal fun so this refers to the person object. and arrow functions do not bind their own this it wil use its sorrunding scope and we are invoking getName poutside scope in scope we have Angular with same variable name
{} are usually interpreted as the function body in an arrow function <br/> JavaScript sees { name: "JS" } and thinks it's a block of code (not an object). So, it doesn't return anything: meaning the function fn() returns undefined
Arrow function inherits this from inc method, which correctly refers to outer
Arrow_Function
let name = 'Outer';
const obj = {
name: 'Inner',
say: () => console.log(this.name),
};
obj.say();
Review the expected output
undefined
we are calling say fun from outside which is arrow one in in global scope we dont have name with var so undeinfed if global name was with var we would have got outer
Closure
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000);
}
Review the expected output
3
3
3
Analyze variable declarations, hoisting behavior, and asynchronous callback timing.
Closure
for (var i = 0; i < 3; i++) {
((j) => {
setTimeout(() => console.log(j), 1000);
})(i);
}
Review the expected output
0
1
2
Analyze variable declarations, hoisting behavior, and asynchronous callback timing.
Closure
function outer() {
let secret = 'I know JS';
return function inner() {
return secret;
};
}
const getSecret = outer();
console.log(getSecret());
Review the expected output
I know JS
Analyze variable declarations, hoisting behavior, and asynchronous callback timing.
Closure
let funcs = [];
for (var i = 0; i < 3; i++) {
funcs.push(() => console.log(i));
}
funcs[0]();
funcs[1]();
Review the expected output
3
3
Analyze variable declarations, hoisting behavior, and asynchronous callback timing.