02 — Variables & Data Types
Q1. What is the key scoping difference between var and let?
if (true) {
var a = 1;
let b = 2;
}
console.log(a);
console.log(b);
- Both
aandbare accessible outside the block; the code logs1then2 -
varis function/global-scoped soalogs1;letis block-scoped so accessingbthrows aReferenceError - Neither is accessible outside the block; both throw
ReferenceError -
letis function-scoped andvaris block-scoped, the reverse of the truth
Show Answer
Answer: B — var is function/global-scoped so a logs 1; let is block-scoped so accessing b throws
Explanation: var ignores block boundaries ({}) and attaches to the nearest function or global scope, so a leaks out of the if block. let (and const) are genuinely block-scoped, so b does not exist outside the if block, and referencing it throws ReferenceError: b is not defined. Option A wrongly assumes both leak. Option D swaps the actual rule.
Q2. What is variable hoisting, as it applies to var?
console.log(x);
var x = 5;
- This throws a
ReferenceErrorbecausexis used before declaration - This logs
undefined, because the declarationvar xis hoisted to the top of scope but the assignment= 5is not - This logs
5, because JavaScript hoists both the declaration and the assignment - This is a
SyntaxErrorbecausevarcannot be declared after use
Show Answer
Answer: B — Logs undefined; the declaration is hoisted but the assignment stays in place
Explanation: During compilation, var x is conceptually moved to the top of its enclosing scope and initialized to undefined; the x = 5 assignment still executes at its original position. So by the time console.log(x) runs, x exists but hasn't been assigned yet. Option A confuses this with let/const's TDZ behavior. Option C is the common misconception that hoisting moves the whole statement, initializer included — it only moves the declaration.
Q3. What is the Temporal Dead Zone (TDZ)?
console.log(y);
let y = 10;
- The period during which a
varvariable holdsundefinedbefore assignment - The span between entering a scope and a
let/constvariable's declaration line, during which accessing the variable throws aReferenceErrorinstead of returningundefined - A deprecated ES5 feature no longer relevant in modern JS
- The time it takes the garbage collector to free an unused variable
Show Answer
Answer: B — The span before a let/const declaration executes, where accessing it throws instead of returning undefined
Explanation: let and const are hoisted too, but unlike var they are not initialized to undefined — they remain in an uninitialized "temporal dead zone" from the top of the block until their declaration executes. Debug: this is why console.log(y) here throws ReferenceError: Cannot access 'y' before initialization rather than silently logging undefined like the var case would. Option A describes var's actual (different) behavior. Option C and D are fabricated.
Q4. Which of the following are JavaScript primitive types? (Select the option that lists ONLY primitives)
-
string,number,Array,boolean -
string,number,boolean,undefined,null,symbol,bigint -
object,function,Date,Map -
string,Number,Boolean(the wrapper objects created vianew)
Show Answer
Answer: B — string, number, boolean, undefined, null, symbol, bigint
Explanation: JavaScript has exactly seven primitive types: string, number, boolean, undefined, null, symbol (ES2015), and bigint (ES2020). Everything else — arrays, plain objects, functions, Date, Map — is a reference type (object under the hood). Option A wrongly includes Array, which is a reference type. Option D is a trap: new String("x") creates a boxed object, not a primitive string — typeof new String("x") is "object", not "string".
Q5. How are primitives and reference types (objects/arrays) different when assigned to a new variable?
let a = { count: 1 };
let b = a;
b.count = 99;
console.log(a.count);
-
1, becausebis a fully independent copy ofa -
99, becauseb = acopies the reference, soaandbpoint to the same object in memory -
undefined, because reassigningb.countbreaks the link toa - This throws a
TypeErrorbecause objects are immutable by default
Show Answer
Answer: B — 99, because objects are assigned/copied by reference
Explanation: Reference types are stored as a pointer to a location in memory; let b = a copies that pointer, not the object's contents, so a and b alias the same object — mutating via one is visible through the other. This is different from primitives, where let x = 1; let y = x; gives y a fully independent copy of the value. Option A describes primitive copy semantics, mistakenly applied to an object. Option C and D are fabricated — nothing here breaks a link or throws.
Q6. What does typeof null evaluate to?
-
"null" -
"undefined" -
"object" -
"boolean"
Show Answer
Answer: C — "object"
Explanation: Debug: this is one of JavaScript's most famous quirks — typeof null === "object" is a bug baked into the language since its first implementation (null was represented internally with the same tag as objects) and has been kept for backward compatibility ever since; it will never be fixed. It does not mean null is actually an object — null === undefined is false, and null has no properties/methods. Option A is the intuitive-but-wrong guess. Option B confuses null with undefined, a related but distinct "empty" value.
Q7. What does typeof NaN evaluate to, and what does NaN === NaN evaluate to?
console.log(typeof NaN);
console.log(NaN === NaN);
-
"NaN"andtrue -
"number"andfalse -
"undefined"andfalse -
"number"andtrue
Show Answer
Answer: B — "number" and false
Explanation: NaN ("Not a Number") is, paradoxically, of type "number" — it represents an invalid numeric result but is still part of the number type. Debug: NaN is the only value in JavaScript that is not equal to itself, by IEEE-754 floating-point spec design, so NaN === NaN is false. To actually test for NaN, use Number.isNaN(x) (not the legacy, coercing global isNaN(x)) or Object.is(x, NaN). Options A, C, and D all misstate one or both facts.
Q8. Which of these correctly demonstrates implicit type coercion in a comparison?
console.log("5" == 5);
console.log("5" === 5);
console.log([] == false);
-
false,false,false -
true,true,true -
true,false,true -
true,false,false
Show Answer
Answer: C — true, false, true
Explanation: == performs type coercion before comparing: "5" == 5 coerces the string to a number, giving true. === performs no coercion, so comparing a string to a number is always false. [] == false is a classic gotcha: [] is coerced to "" (via ToPrimitive), then "" and false are both coerced to numbers (0 and 0), so it's true — despite an array and a boolean seeming completely unrelated. Idiom: this exact unpredictability is why === is recommended over == in production code.
Q9. What is the result of adding a number and a string with +?
console.log(1 + "2");
console.log(1 + 2 + "3");
console.log("1" + 2 + 3);
-
"12","33","123" -
3,"33","15" -
"12","33","33" -
NaN,NaN,"123"
Show Answer
Answer: A — "12", "33", "123"
Explanation: + evaluates left-to-right. 1 + "2": number meets string, so 1 is coerced to "1" and they concatenate to "12". 1 + 2 + "3": 1 + 2 happens first (both numbers) giving 3, then 3 + "3" concatenates to "33". "1" + 2 + 3: "1" + 2 concatenates to "12" first (left-to-right), then "12" + 3 concatenates to "123" — note it does not add 2 + 3 first, because + has no special "do the numbers first" rule; it strictly evaluates left to right. This ordering trap is a frequent source of bugs when mixing types.
Q10. What happens when you access a property on undefined, versus declaring a variable with no initializer?
let x;
console.log(x);
console.log(x.length);
- Logs
undefined, then logs0 - Logs
undefined, then throwsTypeError: Cannot read properties of undefined (reading 'length') - Logs
null, then throwsReferenceError - Both lines throw, because
xis never assigned
Show Answer
Answer: B — Logs undefined, then throws a TypeError
Explanation: A let/var declared without an initializer is automatically set to undefined (not an error to read it, unlike TDZ). But undefined is a primitive with no properties, so attempting to read .length off it throws TypeError. Debug: this exact error is one of the most common in production JS, typically from an object that was expected to exist (e.g., an API response field) but didn't. The fix is optional chaining (x?.length) or an existence check before access. Option A wrongly assumes property access on undefined is safe.
Q11. What is the result of typeof applied to a function?
function greet() {}
console.log(typeof greet);
console.log(greet instanceof Object);
-
"object"andtrue -
"function"andtrue -
"function"andfalse -
"object"andfalse
Show Answer
Answer: B — "function" and true
Explanation: JavaScript special-cases typeof for callable objects, returning "function" even though functions are technically a subtype of object under the hood — which is why greet instanceof Object is also true. This dual nature surprises people who expect typeof categories and the prototype chain to be mutually exclusive. Option A misses that typeof has a dedicated "function" result. Options C and D get the instanceof half wrong — functions genuinely do inherit from Object.prototype via Function.prototype.
Q12. What does 0.1 + 0.2 === 0.3 evaluate to, and why?
console.log(0.1 + 0.2 === 0.3);
console.log(0.1 + 0.2);
-
true; JavaScript numbers are always exact -
false;0.1 + 0.2actually equals0.30000000000000004due to IEEE-754 double-precision floating-point representation, which cannot represent most decimal fractions exactly -
false; this is a bug specific to V8 that other engines don't have -
true; JavaScript rounds floating point arithmetic automatically for===
Show Answer
Answer: B — false; the sum is 0.30000000000000004 due to IEEE-754 binary floating-point representation
Explanation: Portability: JavaScript's number type uses IEEE-754 double-precision floats (as do essentially all mainstream languages using this format — Python, Java, C, etc. exhibit the identical rounding, so this is not V8-specific). Fractions like 0.1 and 0.2 cannot be represented exactly in binary floating point, so tiny representation errors accumulate. The correct way to compare is checking the difference is within an epsilon (e.g., Math.abs(a - b) < Number.EPSILON), not ===. Option A and D are false — there is no automatic rounding. Option C is a plausible-sounding but wrong claim of engine-specific behavior.
Q13. What is the type and value of an empty const declaration attempt?
const PI;
- Valid;
PIisundefineduntil assigned later -
SyntaxError: Missing initializer in const declaration—constrequires an initializer at declaration time - Valid;
PIdefaults to0 - Valid, but
PIenters a permanent TDZ
Show Answer
Answer: B — SyntaxError; const requires an initializer
Explanation: Unlike let and var, const bindings must be initialized in the same statement they're declared — there is no such thing as "declare now, assign later" for const, because a const binding can never be reassigned at all. Option A confuses const with let's allowed no-initializer form. Options C and D invent behavior that doesn't exist — this is a parse-time error, so the code never runs.
Q14. Does const make an object's contents immutable?
const user = { name: "Ana" };
user.name = "Beto";
user = { name: "Carla" };
- Both lines work fine —
constprevents any mutation -
user.name = "Beto"works (mutating properties is allowed);user = {...}throwsTypeError: Assignment to constant variable(reassigning the binding is not allowed) - Both lines throw
TypeError -
user.name = "Beto"throws, but reassigninguserworks
Show Answer
Answer: B — Mutating a property works; reassigning the binding throws
Explanation: const only freezes the binding (the variable name cannot be pointed at a new value) — it says nothing about the mutability of the value itself. Since user still refers to the same object, mutating its properties is completely legal. Idiom: to actually prevent mutation of the object's own properties, use Object.freeze(user) (shallow) in addition to const. Option A is the classic misconception that const means "constant/immutable data." Option D reverses the true behavior.
Q15. Which is the idiomatic default choice for variable declarations in modern JavaScript, and why?
- Always use
var, since it has the widest historical browser support - Default to
constfor bindings that are never reassigned, useletonly when reassignment is genuinely needed, and avoidvar— this communicates intent and avoidsvar's function-scoping/hoisting pitfalls - Always use
leteverywhere for consistency, and reserveconstonly for primitive values - It doesn't matter; all three are functionally interchangeable in modern engines
Show Answer
Answer: B — Default to const, use let only when reassignment is needed, avoid var
Explanation: Idiom: preferring const signals to readers (and tooling/linters) that a binding won't be reassigned, catching accidental reassignment bugs at parse/lint time; let's block scoping avoids the classic var closure-in-loop bug (see Control Flow topic); var's hoisting and function-scoping are considered legacy footguns in modern style guides (Airbnb, StandardJS, etc.) with no upside over let/const. Option A is outdated advice from an era before ES2015 adoption. Option C undersells const's usefulness for objects/arrays too (the binding, not contents, is what's fixed). Option D ignores real, observable scoping differences.
Q16. What is the best-practice way to check if a value is NaN?
function isInvalidNumber(val) {
return val !== val;
}
- Use the global
isNaN(val)function, since it's the shortest and most common option - Use
Number.isNaN(val), because the globalisNaN()first coerces its argument, causing false positives likeisNaN("hello")beingtruewhen the value isn't even numeric;Number.isNaNonly returnstruefor the actualNaNvalue with no coercion - Use
val === NaN, sinceNaNis a well-defined constant - The
val !== valtrick shown above is the only reliable method; all built-ins are broken
Show Answer
Answer: B — Use Number.isNaN(val) to avoid the global function's coercion-related false positives
Explanation: Idiom: the legacy global isNaN() coerces its argument to a number first, so isNaN("hello") is true (because Number("hello") is NaN) even though "hello" was never intended as a numeric check target — a classic false positive. Number.isNaN() (ES2015+) does no coercion, returning true only if the value literally is NaN. Option C is invalid — as established, NaN === NaN is always false, so this check can never succeed. Option D is an overstatement — the val !== val trick does work (it exploits the same self-inequality), but Number.isNaN is the clearer, idiomatic choice; it's not the only reliable method.
Q17. What is the idiomatic way to handle very large integers that exceed Number.MAX_SAFE_INTEGER?
console.log(Number.MAX_SAFE_INTEGER);
console.log(Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2);
- Continue using regular numbers; JavaScript numbers have unlimited precision
- Use the
bigintprimitive type (e.g.,9007199254740993n), which represents arbitrary-precision integers exactly, instead of thenumbertype, which silently loses precision beyond2^53 - 1 - Round the result using
Math.round()to fix precision loss - Store the number as a string and use
parseInt()whenever arithmetic is needed
Show Answer
Answer: B — Use bigint for exact arbitrary-precision integers beyond Number.MAX_SAFE_INTEGER
Explanation: Safety: regular number values are IEEE-754 doubles that can only represent integers exactly up to 2^53 - 1 (Number.MAX_SAFE_INTEGER); beyond that, distinct integers can silently collapse to the same floating-point value (as the true result of the comparison above demonstrates) — a silent correctness bug, not an error you'd notice. bigint (suffix n, ES2020) stores arbitrary-precision integers with no such ceiling, at the cost of not being directly mixable with number in arithmetic (1n + 1 throws TypeError). Option A is false — this is precisely the bug being demonstrated. Option C doesn't address the root precision-loss cause. Option D works but is a clunky workaround compared to a proper primitive type.
Q18. Which best describes idiomatic use of Symbol?
const id = Symbol("userId");
const obj = { [id]: 42, name: "Ana" };
- Symbols are mainly used to create guaranteed-unique property keys (e.g., to avoid accidental collisions with string keys or to define "hidden," non-enumerable-by-default-iteration metadata), not as a general-purpose string replacement
- Symbols are just a stylistic alternative to strings for any object key, interchangeable with strings in all cases
-
Symbol("userId") === Symbol("userId")istrue, so symbols are good for deduplication - Symbols can be implicitly converted to strings via
+, making them convenient for string concatenation
Show Answer
Answer: A — Symbols create guaranteed-unique keys, not a general string replacement
Explanation: Every Symbol() call produces a value unique from every other symbol, even with the same description string — so Symbol("userId") === Symbol("userId") is actually false (making option C the tempting-but-wrong trap). Symbol-keyed properties don't show up in for...in, Object.keys(), or JSON.stringify() by default, making them useful for "semi-private" metadata that won't collide with user-defined string keys. Option B ignores this uniqueness/interop purpose. Option D is false — Symbol values throw TypeError when used with implicit string coercion (e.g., `${sym}` and sym + "" both throw); you must call .toString() or .description explicitly.
Q19. Why does this equality check behave unexpectedly, and what's the idiomatic fix?
function isEmpty(value) {
return value == null;
}
console.log(isEmpty(undefined));
console.log(isEmpty(null));
console.log(isEmpty(0));
-
value == nullis a bug; it should always be written asvalue === nullfor safety -
== nullis actually an idiomatic, intentional pattern: due to type coercion rules,null == undefinedistrue(and only to each other), sovalue == nullcleanly catches bothnullandundefinedin one check without matching falsy-but-defined values like0 - This throws a
TypeErrorbecausenullcannot be compared with== -
isEmpty(0)returnstruebecause0is falsy
Show Answer
Answer: B — == null intentionally catches both null and undefined via a special-cased coercion rule, without matching other falsy values
Explanation: Idiom: per the spec, == has a special rule that null == undefined is true, but null and undefined are == to nothing else (not 0, not "", not false). This makes value == null a widely-used, deliberate idiom to check for "nullish" in one line, predating ??/?.. So isEmpty(undefined) → true, isEmpty(null) → true, isEmpty(0) → false (0 is not loosely equal to null). Option A is overly cautious — this is one of the few places == is considered acceptable/idiomatic rather than a footgun. Option D wrongly assumes truthiness is involved; this comparison uses the == coercion table, not Boolean() coercion.
Q20. What is the best-practice explanation for why typeof is safe to use on a variable that might not be declared at all, but not on one known to be in the TDZ?
console.log(typeof undeclaredVar);
console.log(typeof laterVar);
let laterVar = 5;
- Both lines behave identically, logging
"undefined"in both cases -
typeof undeclaredVarsafely returns"undefined"for a name that was never declared anywhere (a historic idiom for feature-detection); buttypeof laterVarthrowsReferenceErrorbecauselaterVarislet-declared later in scope, so referencing it here falls inside its Temporal Dead Zone even undertypeof - Both lines throw
ReferenceError -
typeofnever throws under any circumstances, by design, for any identifier
Show Answer
Answer: B — typeof on a truly undeclared name is safe; but typeof on a let/const name still in its TDZ throws
Explanation: Idiom: typeof someGlobalThatMightNotExist is a long-standing, safe idiom (e.g., typeof window !== "undefined" to detect a browser environment) specifically because typeof was designed not to throw for names that were never declared. However, this safety does not extend to TDZ: if the identifier is declared later in the same scope via let/const, the engine already knows about it and considers it "temporally dead" until its declaration line, so even the normally-safe typeof throws. Option D is the common but incorrect belief that typeof is unconditionally throw-proof.