JavaScript Miscellaneous — Tricky Interview Questions
Q1: Hoisting Order
javascriptconsole.log(typeof foo);
var foo = 'string';
function foo() {}
console.log(typeof foo);Output: 'function' then 'string'
Why: Function declarations hoist above var. After hoisting: function foo is declared first. Then var foo is ignored (already declared). At runtime: typeof foo = 'function'. Then foo = 'string' assignment executes. Second typeof foo = 'string'.
Q2: TDZ Scope
javascriptlet x = 1;
{
console.log(x); // ?
let x = 2;
}Output: ReferenceError: Cannot access 'x' before initialization
Why: Inside the block, let x = 2 creates a NEW x scoped to the block. This x is in TDZ until line let x = 2. Even though outer x = 1 exists, the inner declaration "shadows" it — but is in TDZ.
Q3: Coercion Surprise
javascriptconsole.log(1 + '2' + 3);
console.log(1 + 2 + '3');
console.log(+'3' + 3);
console.log([] + []);
console.log([] + {});
console.log({} + []);Outputs:
'123'—1 + '2'='12','12' + 3='123''33'—1 + 2=3,3 + '3'='33'6— unary+converts'3'to3,3 + 3 = 6''—[] → '','' + ''='''[object Object]'—[] → '',{} → '[object Object]','' + '[object Object]'0—{}treated as EMPTY BLOCK in statement context,+[]=+''=0
Q4: NaN Comparison
javascriptconsole.log(NaN === NaN);
console.log(NaN == NaN);
console.log(isNaN('hello'));
console.log(Number.isNaN('hello'));
console.log(Number.isNaN(NaN));Outputs: false, false, true, false, true
Why: NaN is never equal to itself. isNaN() coerces first ('hello' → NaN → true). Number.isNaN() only returns true if the value IS the actual NaN number value.
Q5: typeof on Undeclared Variable
javascriptconsole.log(typeof undeclaredVariable);
console.log(undeclaredVariable);Output: 'undefined' then ReferenceError
Why: typeof is safe — doesn't throw for undeclared variables, returns 'undefined'. Direct access to an undeclared variable throws ReferenceError.
Q6: Object Equality
javascriptconst a = {};
const b = {};
const c = a;
console.log(a === b); // ?
console.log(a === c); // ?
console.log(a == b); // ?Output: false, true, false
Why: a and b are different objects in memory. c references the same object as a. == with objects compares references, not content.
Q7: for...in Surprises
javascriptconst arr = [1, 2, 3];
arr.foo = 'bar'; // add non-index property
for (const i in arr) {
console.log(i);
}Output: '0', '1', '2', 'foo'
Why: for...in enumerates ALL enumerable properties including non-index ones. This is why you should never use for...in for arrays. Use for...of, forEach, or standard for loop.
Q8: Comma Operator
javascriptconst x = (1, 2, 3, 4, 5);
console.log(x); // ?
let a = 0;
for (let i = 0, j = 10; i < 3; i++, j--) {
a += i + j;
}
console.log(a); // ?Output: 5 and 33
Why: The comma operator evaluates each expression and returns the LAST. (1,2,3,4,5) returns 5.
Loop: (0+10) + (1+9) + (2+8) = 10 + 10 + 10 = Wait, j starts at 10:
- i=0, j=10: a += 0+10 = 10
- i=1, j=9: a += 1+9 = 10 → total 20
- i=2, j=8: a += 2+8 = 10 → total 30
Hmm, a = 30. Let me recount: 0+10=10, 1+9=10, 2+8=10. Total=30. Output: 5 and 30.
Q9: Short-Circuit Evaluation
javascriptconst a = null;
const b = a || 'default';
const c = a && a.value;
const d = a ?? 'fallback';
const e = 0 || 'fallback';
const f = 0 ?? 'fallback';Values: 'default', null, 'fallback', 'fallback', 0
||returns first truthy or last value (nullis falsy →'default')&&returns first falsy or last value (nullis falsy →null)??(nullish coalescing) returns right side only if left isnull/undefined0 || 'fallback'→'fallback'(0 is falsy)0 ?? 'fallback'→0(0 is NOT null/undefined)
Q10: delete Operator
javascriptconst obj = { a: 1, b: 2 };
delete obj.a;
console.log(obj); // ?
const arr = [1, 2, 3];
delete arr[1];
console.log(arr); // ?
console.log(arr.length); // ?Output:
{ b: 2 }[1, empty, 3](sparse array — index 1 is deleted, not shifted)3— length unchanged!
Q11: void Operator
javascriptconsole.log(void 0); // ?
console.log(void 'hello'); // ?
console.log(void(1 + 2)); // ?Output: undefined, undefined, undefined
void expression evaluates the expression and always returns undefined. Common use: void 0 as a safe way to get undefined (before undefined could be reassigned in old JS).
Q12: Object Property Shorthand
javascriptconst x = 1, y = 2;
const obj = { x, y }; // shorthand
console.log(obj); // ?
function makeObj(name, age) {
return { name, age }; // shorthand
}
console.log(makeObj('Alice', 30)); // ?Output: { x: 1, y: 2 }, { name: 'Alice', age: 30 }
Q13: Optional Chaining Edge Cases
javascriptconst user = { profile: null };
console.log(user?.profile?.name); // ?
console.log(user?.profile?.name ?? 'Anonymous'); // ?
console.log(user?.scores?.[0]); // ?
console.log(user?.getScore?.()); // ?Output: undefined, 'Anonymous', undefined, undefined
Optional chaining short-circuits and returns undefined without throwing when it hits null/undefined.
Q14: Destructuring Default Values
javascriptconst { a = 1, b = 2, c = 3 } = { a: 10, b: null };
console.log(a, b, c); // ?Output: 10 null 3
Why: Default values kick in only when the value is undefined. b: null — null is NOT undefined, so the default 2 is NOT used. c is not in the source object → undefined → default 3 used.
Q15: Computed Property Keys
javascriptconst key = 'name';
const prefix = 'user';
const obj = {
[key]: 'Alice',
[`${prefix}Id`]: 42,
[Symbol.iterator]() { return this; }
};
console.log(obj.name); // ?
console.log(obj.userId); // ?Output: 'Alice', 42
Q16: String vs String Object
javascriptconst str1 = 'hello';
const str2 = new String('hello');
console.log(typeof str1); // ?
console.log(typeof str2); // ?
console.log(str1 === str2); // ?
console.log(str1 == str2); // ?Output: 'string', 'object', false, true
new String() creates an object wrapper. typeof = 'object'. === fails (different types). == converts the String object to primitive 'hello' then compares.
Q17: Chained Ternary
javascriptconst score = 75;
const grade = score >= 90 ? 'A'
: score >= 80 ? 'B'
: score >= 70 ? 'C'
: score >= 60 ? 'D'
: 'F';
console.log(grade); // ?Output: 'C' (75 >= 70 is first true condition)
Q18: Symbol Equality
javascriptconst s1 = Symbol('desc');
const s2 = Symbol('desc');
const s3 = s1;
console.log(s1 === s2); // ?
console.log(s1 === s3); // ?
console.log(typeof s1); // ?Output: false, true, 'symbol'
Symbols are always unique — Symbol('desc') creates a new unique symbol each time. s3 references the same symbol as s1.
Q19: Array Holes (Sparse Arrays)
javascriptconst arr = [1, , 3]; // hole at index 1
console.log(arr.length); // ?
console.log(arr[1]); // ?
console.log(1 in arr); // ?
console.log(arr.map(x => x * 2)); // ?Output: 3, undefined, false, [2, empty, 6]
Array holes are different from undefined — 1 in arr is false (property doesn't exist). map/forEach/filter skip holes.
Q20: typeof vs instanceof for Primitives
javascriptconsole.log(typeof 42 === 'number'); // ?
console.log(42 instanceof Number); // ?
console.log(new Number(42) instanceof Number); // ?
console.log(typeof new Number(42)); // ?Output: true, false, true, 'object'
Primitive 42 is not an instance of Number — it's not an object. new Number(42) creates an object wrapper — IS an instance of Number, but typeof returns 'object'.
Q21: [1, 2, 3].map(parseInt) — The Classic Trap
javascriptconsole.log([1, 2, 3].map(parseInt));Output: [1, NaN, NaN]
Why: map calls parseInt(element, index, array). So it becomes:
parseInt('1', 0)→ radix 0 means 10 →1parseInt('2', 1)→ radix 1 is invalid →NaNparseInt('3', 2)→ base 2, but'3'is not a valid binary digit →NaN
Fix:
javascript[1, 2, 3].map(Number) // [1, 2, 3]
[1, 2, 3].map(n => parseInt(n, 10)) // [1, 2, 3]Q22: Floating Point Gotcha
javascriptconsole.log(0.1 + 0.2);
console.log(0.1 + 0.2 === 0.3);
console.log(0.1 + 0.2 === 0.30000000000000004);Output: 0.30000000000000004, false, true
Why: IEEE 754 double-precision floating point cannot represent 0.1 or 0.2 exactly. They're recurring fractions in binary.
Fix:
javascript// Option 1: epsilon comparison
Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON // true
// Option 2: round to known precision
parseFloat((0.1 + 0.2).toFixed(10)) === 0.3 // true
// Option 3: work in integers
(10 + 20) / 100 === 30 / 100 // trueQ23: [] == ![] is true
javascriptconsole.log([] == ![]);
console.log([] == false);
console.log([] == 0);
console.log('' == false);Output: true, true, true, true
Why — step by step:
![]→false(empty array is truthy,!truthy=false)[] == false→ Abstract equality:false→0,[]→''→00 == 0→true
Coercion chain:
[] == ![]
[] == false // ![] is false
[] == 0 // false → Number → 0
'' == 0 // [] → '' (Array.prototype.toString)
0 == 0 // '' → Number → 0
true // ✓This is why === (strict equality) exists. Never use == with objects.
Q24: Sort Is Lexicographic by Default
javascriptconst nums = [10, 9, 2, 1, 100, 20];
console.log(nums.sort());
console.log([1, 30, 4, 21].sort());Output: [1, 10, 100, 2, 20, 9] and [1, 21, 30, 4]
Why: Array.prototype.sort() converts elements to strings and sorts lexicographically (character by character). '10' < '9' because '1' < '9'.
Fix:
javascriptnums.sort((a, b) => a - b); // ascending: [1, 2, 9, 10, 20, 100]
nums.sort((a, b) => b - a); // descending: [100, 20, 10, 9, 2, 1]
// String sort (locale-aware):
['banana', 'Apple', 'cherry'].sort((a, b) => a.localeCompare(b));
// ['Apple', 'banana', 'cherry']Q25: JSON.stringify Silently Drops Values
javascriptconst obj = {
a: 1,
b: undefined,
c: function() {},
d: Symbol('id'),
e: null,
f: NaN,
g: Infinity,
};
console.log(JSON.stringify(obj));Output: {"a":1,"e":null,"f":null,"g":null}
Why:
undefined, functions, Symbols → omitted from objects (silently dropped)NaN,Infinity→ converted tonullnull→ kept asnull
In arrays:
javascriptJSON.stringify([undefined, function(){}, Symbol(), null, NaN])
// '[null,null,null,null,null]'
// undefined/function/Symbol in arrays → null (can't omit array slots)Circular reference:
javascriptconst a = {};
a.self = a;
JSON.stringify(a); // TypeError: Converting circular structure to JSONQ26: Object.assign Is Shallow
javascriptconst target = { a: 1, nested: { x: 10 } };
const source = { b: 2, nested: { y: 20 } };
const result = Object.assign({}, target, source);
console.log(result);
result.nested.y = 99;
console.log(source.nested.y); // ?Output: { a: 1, nested: { y: 20 }, b: 2 } then 99
Why: Object.assign copies property references, not deep copies. result.nested and source.nested point to the SAME object. Mutating one mutates the other.
Same applies to spread: { ...target, ...source } is also shallow.
Deep clone options:
javascript// 1. structuredClone (native, handles most types)
const deep = structuredClone(obj);
// 2. JSON round-trip (loses undefined/functions/Dates become strings)
const deep = JSON.parse(JSON.stringify(obj));
// 3. Lodash _.cloneDeepQ27: Arguments Object vs Rest Parameters
javascriptfunction withArguments() {
console.log(arguments);
console.log(Array.isArray(arguments));
const arr = Array.from(arguments);
return arr.reduce((a, b) => a + b, 0);
}
const withRest = (...args) => {
console.log(args);
console.log(Array.isArray(args));
return args.reduce((a, b) => a + b, 0);
};
withArguments(1, 2, 3);
withRest(1, 2, 3);Output:
[Arguments] { '0': 1, '1': 2, '2': 3 }
false
[1, 2, 3]
trueKey differences:
| Feature | arguments |
...rest |
|---|---|---|
| Type | array-like object | real Array |
| Arrow functions | ❌ not available | ✅ works |
Has .map/.filter |
❌ no | ✅ yes |
| Contains all args | always | from rest position on |
Q28: Getter Is Evaluated Lazily
javascriptconst obj = {
get now() {
return Date.now();
},
cached: Date.now(), // evaluated once at definition
};
const t1 = obj.now;
const t2 = obj.now;
console.log(t1 === t2); // ?
console.log(obj.cached === obj.cached); // ?Output: false, true
obj.now calls the getter each time — returns a fresh Date.now() on every access. obj.cached is a plain property set once.
javascript// Memoized getter with defineProperty:
const obj2 = {};
Object.defineProperty(obj2, 'expensive', {
get() {
const value = computeExpensiveThing();
Object.defineProperty(this, 'expensive', { value, writable: false });
return value;
},
configurable: true,
});
// First access: computes. Subsequent: returns cached value.Q29: typeof class Returns 'function'
javascriptclass Foo {}
console.log(typeof Foo); // ?
console.log(typeof Foo.prototype); // ?
console.log(typeof new Foo()); // ?
console.log(Foo instanceof Function); // ?Output: 'function', 'object', 'object', true
Classes are syntactic sugar over constructor functions. typeof a class is 'function'. The class itself is a Function instance.
Q30: Prototype Pollution via proto
javascriptconst userInput = '{"__proto__": {"isAdmin": true}}';
const parsed = JSON.parse(userInput);
const obj = Object.assign({}, parsed);
console.log(obj.isAdmin); // ?
console.log({}.isAdmin); // ?Output: undefined, undefined
JSON.parse does NOT pollute prototypes — it creates own properties. Object.assign copies the __proto__ key as a string key (not as the prototype setter because source is a plain object from JSON.parse — the __proto__ is a regular string-keyed property here).
Actual pollution (the real danger):
javascript// THIS is dangerous:
const malicious = JSON.parse('{"__proto__": {"isAdmin": true}}');
Object.assign(Object.prototype, malicious.__proto__); // explicit merge = polluted!
console.log({}.isAdmin); // true — all objects polluted!
// Safe merge:
const safe = Object.assign(Object.create(null), parsed); // null prototypeQ31: in Operator Checks Prototype Chain
javascriptfunction Animal(name) {
this.name = name;
}
Animal.prototype.type = 'animal';
const dog = new Animal('Rex');
console.log('name' in dog); // ?
console.log('type' in dog); // ?
console.log(dog.hasOwnProperty('name')); // ?
console.log(dog.hasOwnProperty('type')); // ?Output: true, true, true, false
in checks own properties AND the entire prototype chain. hasOwnProperty checks only own properties.
Q32: Array Destructuring with Skips and Defaults
javascriptconst [a, , b, c = 'default', ...rest] = [1, 2, 3, undefined, 5, 6];
console.log(a); // ?
console.log(b); // ?
console.log(c); // ?
console.log(rest); // ?Output: 1, 3, 'default', [5, 6]
, ,skips index 1 (value 2)c = 'default'— default activates because index 3 isundefined...restcollects remaining:[5, 6]
Q33: Object.freeze vs const
javascriptconst obj = Object.freeze({ a: 1, nested: { b: 2 } });
obj.a = 99; // silently ignored (not in strict mode)
obj.nested.b = 99; // WORKS — freeze is shallow!
obj.c = 3; // silently ignored
console.log(obj.a); // ?
console.log(obj.nested.b); // ?
console.log(obj.c); // ?Output: 1, 99, undefined
Object.freeze prevents adding/deleting/modifying own properties. It does NOT deep-freeze nested objects. The const keyword prevents REASSIGNMENT of the binding — it doesn't freeze the object.
javascript// const vs freeze:
const x = { a: 1 };
x.a = 2; // ✅ allowed — const prevents x = {...}, not mutation
x = {}; // ❌ TypeError — can't reassign const binding
// Deep freeze:
function deepFreeze(obj) {
Object.getOwnPropertyNames(obj).forEach(name => {
const val = obj[name];
if (typeof val === 'object' && val !== null) deepFreeze(val);
});
return Object.freeze(obj);
}Q34: Tagged Template Literals
javascriptfunction tag(strings, ...values) {
console.log(strings);
console.log(values);
return strings.reduce((result, str, i) =>
result + str + (values[i] !== undefined ? `[${values[i]}]` : ''), '');
}
const name = 'Alice';
const age = 30;
const result = tag`Hello ${name}, you are ${age} years old`;
console.log(result);Output:
['Hello ', ', you are ', ' years old']
['Alice', 30]
'Hello [Alice], you are [30] years old'strings is the array of string parts (always values.length + 1). values are the interpolated expressions. Used in: styled-components, SQL template literals (auto-parameterization), i18n.
Q35: Implicit Return vs Block Body in Arrow Functions
javascriptconst f1 = x => x * 2; // implicit return
const f2 = x => { x * 2 }; // block body, no return → undefined
const f3 = x => ({ value: x }); // return object literal (must wrap in parens)
const f4 = x => { return x * 2 }; // explicit return
console.log(f1(3)); // ?
console.log(f2(3)); // ?
console.log(f3(3)); // ?Output: 6, undefined, { value: 3 }
{ } after => is always a block body. To return an object literal, wrap it in ( ).
Q36: Spread in Function Call vs Array
javascriptfunction sum(a, b, c) { return a + b + c; }
const nums = [1, 2, 3];
console.log(sum(...nums)); // ? (spread as arguments)
console.log(Math.max(...nums)); // ?
console.log(Math.max(nums)); // ?
const combined = [0, ...nums, 4];
console.log(combined); // ?Output: 6, 3, NaN, [0, 1, 2, 3, 4]
Math.max(nums) passes an array as a single argument — Math.max([1,2,3]) → NaN. Math.max(...nums) spreads to Math.max(1, 2, 3) → 3.
Q37: Chaining with null/undefined — Optional Chaining Internals
javascriptconst data = {
users: [
{ name: 'Alice', address: { city: 'NYC' } },
{ name: 'Bob' }
]
};
console.log(data.users[0]?.address?.city); // ?
console.log(data.users[1]?.address?.city); // ?
console.log(data.users[2]?.address?.city); // ?
console.log(data.users?.[0]?.name); // ?
// Calling a method that might not exist:
console.log(data.users[0]?.getScore?.()); // ?Output: 'NYC', undefined, undefined, 'Alice', undefined
Optional chaining short-circuits the moment it hits null/undefined — the rest of the chain is not evaluated at all.
Q38: WeakMap vs Map — Garbage Collection
javascriptlet obj = { name: 'temp' };
const map = new Map();
const weakMap = new WeakMap();
map.set(obj, 'in map');
weakMap.set(obj, 'in weakmap');
obj = null; // remove our reference
// What happens now?
// map: obj still lives — Map holds a strong reference → memory leak possible
// weakMap: obj can be GC'd — WeakMap holds a WEAK reference
console.log(map.size); // 1 (still there)
// weakMap.has(obj) — can't check, obj is null nowUse WeakMap for:
- DOM element → metadata mapping (element removed → data auto-cleaned)
- Private class data (class instance as key)
- Memoization where the cache should die with the object
Key difference: WeakMap keys must be objects, is not iterable, has no .size.
Q39: String Immutability
javascriptlet str = 'hello';
str[0] = 'H'; // silently ignored
str.toUpperCase(); // returns NEW string, doesn't mutate
console.log(str); // ?
console.log(str.toUpperCase()); // ?
console.log(str); // ?Output: 'hello', 'HELLO', 'hello'
Strings are immutable primitives. str[0] = 'H' does nothing. String methods always return new strings. To "mutate" a string, reassign the variable.
Q40: Label Statement
javascriptouter: for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (j === 1) break outer;
console.log(i, j);
}
}Output: 0 0
break outer breaks out of the labeled (outer) loop, not just the inner loop. Labels can also be used with continue outer to skip to the next iteration of the outer loop.
Q41: Number Edge Cases
javascriptconsole.log(Number.MAX_SAFE_INTEGER); // ?
console.log(Number.MAX_SAFE_INTEGER + 1); // ?
console.log(Number.MAX_SAFE_INTEGER + 2); // ?
console.log(Number.isFinite(Infinity)); // ?
console.log(Number.isFinite(1 / 0)); // ?
console.log(isFinite('5')); // ?
console.log(Number.isFinite('5')); // ?Output: 9007199254740991, 9007199254740992, 9007199254740992, false, false, true, false
Past MAX_SAFE_INTEGER, integers can't be represented exactly. MAX_SAFE_INTEGER + 1 === MAX_SAFE_INTEGER + 2 because both round to the same float.
isFinite('5') → coerces → true. Number.isFinite('5') → no coerce → false. Always prefer Number.isFinite / Number.isNaN.
Use BigInt for large integers:
javascriptconst big = BigInt(Number.MAX_SAFE_INTEGER) + 1n;
console.log(big); // 9007199254740992nQ42: Function Hoisting vs var Hoisting — Same Name
javascriptvar foo = 10;
function foo() { return 20; }
console.log(typeof foo);
console.log(foo);Output: 'number', 10
Why: Function declarations hoist ABOVE var declarations. After hoisting, the execution order is: function foo is declared → var foo is ignored (already declared) → foo = 10 assignment runs at runtime. By the time console.log executes, foo is 10.
Q43: let/const in for Loops — Fresh Binding Per Iteration
javascriptconst fns = [];
for (let i = 0; i < 3; i++) {
fns.push(() => i);
}
console.log(fns.map(f => f()));
const fns2 = [];
for (var j = 0; j < 3; j++) {
fns2.push(() => j);
}
console.log(fns2.map(f => f()));Output: [0, 1, 2], [3, 3, 3]
Why: let creates a NEW binding for each loop iteration — each closure captures its own i. var has a single binding shared across all iterations — all closures see the final value 3.
Q44: TDZ in Default Parameters
javascriptfunction greet(a = b, b = 2) {
return a + b;
}
console.log(greet(1, 2)); // ?
console.log(greet()); // ?Output: 3, then ReferenceError: Cannot access 'b' before initialization
Why: Default parameter expressions are evaluated left-to-right. When greet() is called, a = b tries to read b — but b hasn't been initialized yet (it's in its own TDZ). Default parameters have their own scope and TDZ rules.
Q45: Destructuring — Renaming and Nested Defaults
javascriptconst { a: x, b: { c: y = 42 } = {} } = { a: 1 };
console.log(typeof a); // ?
console.log(x); // ?
console.log(y); // ?Output: 'undefined', 1, 42
Why: a: x renames a to x — a is NOT declared as a variable. b is missing from the source → falls back to {} → c is missing → falls back to default 42. The variable a was never declared — typeof a is 'undefined'.
Q46: Spread Does Not Copy Getters — It Evaluates Them
javascriptconst source = {
get value() {
console.log('getter called');
return 42;
}
};
const copy = { ...source };
console.log(Object.getOwnPropertyDescriptor(copy, 'value'));
console.log(copy.value);Output:
getter called
{ value: 42, writable: true, enumerable: true, configurable: true }
42Why: Spread calls the getter during the copy and stores the RESULT as a plain data property. The getter function itself is NOT copied. If you need to copy property descriptors, use Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)).
Q47: Optional Chaining Does Not Short-Circuit Assignment
javascriptconst obj = {};
obj?.foo = 'bar'; // ?Output: SyntaxError: Invalid left-hand side in assignment
Why: Optional chaining (?.) is not valid on the LEFT side of an assignment. It's a read-only expression — you can't use it to conditionally assign properties. You need explicit checks: if (obj) obj.foo = 'bar';
Q48: for...in Iterates Strings Too
javascriptfor (const ch in 'abc') {
console.log(ch);
}
for (const ch of 'abc') {
console.log(ch);
}Output:
0
1
2
a
b
cWhy: for...in iterates over enumerable property KEYS — for strings, those are the character indices ('0', '1', '2'). for...of iterates over values — for strings, those are individual characters. for...of works on any iterable (strings, arrays, Maps, Sets). for...in works on any object's enumerable keys.
Q49: WeakSet — Cannot Store Primitives
javascriptconst ws = new WeakSet();
ws.add({ id: 1 }); // ✅ OK
ws.add([1, 2, 3]); // ✅ OK (arrays are objects)
try {
ws.add('hello'); // ?
} catch (e) {
console.log(e.message);
}
console.log(ws.has({ id: 1 })); // ?Output: 'Invalid value used in weak set', then false
Why: WeakSet (and WeakMap keys) only accept objects — primitives can't be weakly held because they aren't garbage collected. ws.has({ id: 1 }) is false because it's a DIFFERENT object in memory, not the one we added.
Q50: Proxy — Intercepting Property Access
javascriptconst handler = {
get(target, prop, receiver) {
if (prop in target) return target[prop];
return `Property '${prop}' not found`;
},
set(target, prop, value) {
if (typeof value !== 'number') {
throw new TypeError(`Expected number, got ${typeof value}`);
}
target[prop] = value;
return true; // must return true for success
}
};
const obj = new Proxy({}, handler);
obj.x = 10;
console.log(obj.x); // ?
console.log(obj.missing); // ?
try {
obj.y = 'hello';
} catch (e) {
console.log(e.message);
}Output: 10, "Property 'missing' not found", 'Expected number, got string'
Why: Proxy intercepts fundamental operations. The get trap fires on every property read. The set trap fires on every write. Forgetting return true in a set trap causes a TypeError in strict mode.
Q51: Reflect.ownKeys — Includes Symbols and Non-Enumerable
javascriptconst sym = Symbol('hidden');
const obj = { a: 1, [sym]: 2 };
Object.defineProperty(obj, 'secret', {
value: 3,
enumerable: false
});
console.log(Object.keys(obj)); // ?
console.log(Object.getOwnPropertyNames(obj)); // ?
console.log(Reflect.ownKeys(obj)); // ?Output:
['a']['a', 'secret']['a', 'secret', Symbol(hidden)]
Why: Object.keys → own enumerable string keys only. getOwnPropertyNames → own string keys (enumerable + non-enumerable). Reflect.ownKeys → ALL own keys (strings + symbols, enumerable + non-enumerable).
Q52: Symbol.for — Global Symbol Registry
javascriptconst s1 = Symbol('app');
const s2 = Symbol('app');
const s3 = Symbol.for('app');
const s4 = Symbol.for('app');
console.log(s1 === s2); // ?
console.log(s3 === s4); // ?
console.log(s1 === s3); // ?
console.log(Symbol.keyFor(s3)); // ?
console.log(Symbol.keyFor(s1)); // ?Output: false, true, false, 'app', undefined
Why: Symbol() always creates a unique symbol. Symbol.for() looks up the global registry — if a symbol with that key exists, it returns the same one. s1 was created with Symbol(), not Symbol.for(), so it's not in the registry and Symbol.keyFor returns undefined.
Q53: Module Scope — import is Hoisted and Live-Bound
javascript// counter.mjs
export let count = 0;
export function increment() { count++; }
// main.mjs
import { count, increment } from './counter.mjs';
console.log(count); // 0
increment();
console.log(count); // 1 — live binding!
count = 10; // TypeError: Assignment to constant variableOutput: 0, 1, then TypeError
Why: ES module import bindings are LIVE — they reflect the current value in the exporting module. But imported bindings are READ-ONLY from the importing module's perspective. Also, import statements are hoisted to the top of the module regardless of where they appear in code.
Q54: Tagged Template Literals — Raw Strings
javascriptfunction raw(strings) {
console.log(strings[0]);
console.log(strings.raw[0]);
}
raw`Hello\nWorld`;Output:
Hello
World
Hello\nWorldWhy: strings[0] processes escape sequences — \n becomes an actual newline. strings.raw[0] preserves the raw source text — \n stays as a literal backslash + n. This is how String.raw works: String.raw`Hello\nWorld` returns the literal string Hello\nWorld.
Q55: Generator — yield Is an Expression
javascriptfunction* gen() {
const x = yield 'first';
console.log('x =', x);
const y = yield 'second';
console.log('y =', y);
return 'done';
}
const g = gen();
console.log(g.next()); // ?
console.log(g.next('A')); // ?
console.log(g.next('B')); // ?
console.log(g.next()); // ?Output:
{ value: 'first', done: false }
x = A
{ value: 'second', done: false }
y = B
{ value: 'done', done: true }
{ value: undefined, done: true }Why: The first next() runs until the first yield and returns 'first'. The argument to the NEXT next('A') call becomes the return value of that yield expression — so x = 'A'. After return, subsequent next() calls always return { value: undefined, done: true }.
Q56: Regex lastIndex Gotcha with Global Flag
javascriptconst regex = /a/g;
console.log(regex.test('abc')); // ?
console.log(regex.lastIndex); // ?
console.log(regex.test('abc')); // ?
console.log(regex.lastIndex); // ?
console.log(regex.test('abc')); // ?Output: true, 1, false, 0, true
Why: With the /g (or /y) flag, regex.test() and regex.exec() are STATEFUL. They update lastIndex after each match. On the second call, it starts searching at index 1 — no 'a' at or after index 1 → false, and lastIndex resets to 0. This is a classic bug when reusing a global regex across multiple strings. Fix: create a new regex each time, or manually reset regex.lastIndex = 0.
Q57: eval and Scope — Direct vs Indirect
javascriptconst x = 'global';
function directEval() {
const x = 'local';
return eval('x');
}
function indirectEval() {
const x = 'local';
const myEval = eval;
return myEval('x');
}
console.log(directEval()); // ?
console.log(indirectEval()); // ?Output: 'local', 'global'
Why: A DIRECT call to eval (using the literal name eval) executes in the local scope — sees x = 'local'. An INDIRECT call (assigned to another variable, called via (0, eval)('x'), etc.) executes in the GLOBAL scope — sees the global x. Direct eval can also introduce new variables into the local scope, which is why it defeats engine optimizations.
Q58: with Statement — Scope Injection (Deprecated)
javascript'use strict';
with ({ a: 1 }) { console.log(a); }Output: SyntaxError: Strict mode code may not include a with statement
Non-strict mode:
javascriptconst obj = { a: 1, b: 2 };
with (obj) {
console.log(a); // 1
console.log(b); // 2
c = 3; // creates global variable! (not on obj)
}
console.log(obj.c); // undefined
console.log(c); // 3 (leaked to global)Why: with injects the object's properties into the scope chain. It's banned in strict mode because it makes scope unpredictable — the engine can't know at compile time whether a refers to the object's property or a variable. Assignments to non-existent properties leak to global scope.
Q59: Labeled Statement with Block
javascriptfoo: {
console.log('before');
break foo;
console.log('after');
}
console.log('outside');Output: 'before', 'outside'
Why: Labels can be applied to any block statement, not just loops. break foo exits the labeled block early — 'after' is never reached. This is rarely used in practice but is valid JavaScript. continue with labels only works inside loops.
Q60: void Operator in Arrow Functions
javascriptconst sideEffect = () => void console.log('fired');
const result = sideEffect();
console.log(result);
const arr = [1, 2, 3];
arr.forEach(x => void x);
console.log(arr);Output: 'fired', undefined, [1, 2, 3]
Why: void expr evaluates expr and returns undefined. void console.log('fired') runs the log (side effect) but the function returns undefined. Useful for arrow functions where you want a side effect but explicitly no return value — prevents accidentally returning something from a one-liner arrow.
Q61: delete on Variables and Properties
javascriptvar x = 10;
let y = 20;
globalThis.z = 30;
console.log(delete x); // ?
console.log(delete y); // ?
console.log(delete globalThis.z); // ?
console.log(typeof x); // ?
console.log(typeof z); // ?Output: false, false, true, 'number', 'undefined'
Why: delete only works on OBJECT PROPERTIES, not variable declarations. var and let create non-configurable bindings → delete returns false and does nothing. globalThis.z is a configurable property → delete succeeds and returns true. Note: in non-strict mode, delete on a variable silently fails. In strict mode, delete x on a declared variable is a SyntaxError.
Q62: Object.seal vs Object.freeze
javascriptconst sealed = Object.seal({ a: 1, b: { c: 2 } });
const frozen = Object.freeze({ a: 1, b: { c: 2 } });
sealed.a = 99; // ?
sealed.newProp = 42; // ?
delete sealed.a; // ?
sealed.b.c = 99; // ?
console.log(sealed.a); // ?
console.log(sealed.newProp); // ?
console.log(sealed.b.c); // ?
frozen.a = 99; // silently fails
console.log(frozen.a); // ?Output: 99, undefined, 99, 1
Why:
| Operation | Object.seal |
Object.freeze |
|---|---|---|
| Modify existing | ✅ allowed | ❌ no |
| Add new property | ❌ no | ❌ no |
| Delete property | ❌ no | ❌ no |
| Nested mutation | ✅ allowed | ✅ allowed (shallow!) |
Both are SHALLOW — nested objects are not sealed/frozen. Object.isFrozen() and Object.isSealed() check the status.
Q63: structuredClone — What It Can't Clone
javascript// ✅ Works:
const cloned = structuredClone({
date: new Date(),
regex: /abc/gi,
map: new Map([['key', 'val']]),
set: new Set([1, 2, 3]),
nested: { deep: { arr: [1, 2] } },
buffer: new ArrayBuffer(8),
});
console.log(cloned.date instanceof Date); // true
console.log(cloned.map instanceof Map); // true
// ❌ Throws:
try {
structuredClone({ fn: () => {} });
} catch (e) {
console.log(e.message); // ?
}
try {
structuredClone({ sym: Symbol('x') });
} catch (e) {
console.log(e.message); // ?
}
// ❌ Also cannot clone:
// - DOM nodes
// - Proxy objects
// - Property descriptors (getters/setters become plain values)
// - Prototype chain (result is always plain Object)Output: true, true, then two DataCloneError messages about uncloneable types.
Why: structuredClone uses the structured clone algorithm (same as postMessage). It handles Date, RegExp, Map, Set, ArrayBuffer, and cyclic references — but CANNOT handle functions, Symbols, DOM nodes, or Proxy objects. It also strips the prototype chain and property descriptors.
Q64: for...of on Objects — TypeError
javascriptconst obj = { a: 1, b: 2, c: 3 };
try {
for (const val of obj) {
console.log(val);
}
} catch (e) {
console.log(e.message); // ?
}
// Fix: make it iterable
for (const [key, val] of Object.entries(obj)) {
console.log(key, val);
}Output: 'obj is not iterable', then a 1, b 2, c 3
Why: Plain objects are NOT iterable — they don't have a [Symbol.iterator] method. for...of requires an iterable (arrays, strings, Maps, Sets, generators). Use Object.entries(), Object.keys(), or Object.values() to iterate over objects with for...of. Alternatively, add [Symbol.iterator] to make any object iterable.
Q65: Regex exec with Capture Groups
javascriptconst regex = /(\d{4})-(\d{2})-(\d{2})/;
const match = regex.exec('Date: 2025-03-14');
console.log(match[0]); // ?
console.log(match[1]); // ?
console.log(match[2]); // ?
console.log(match[3]); // ?
console.log(match.index); // ?
// Named capture groups (ES2018):
const named = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const m = named.exec('2025-03-14');
console.log(m.groups.year); // ?
console.log(m.groups.month); // ?Output: '2025-03-14', '2025', '03', '14', 6, '2025', '03'
Why: match[0] is the full match. match[1..N] are capture groups in order. match.index is the position of the match in the input string. Named groups provide match.groups for readable access.
Q66: Generator — return() and throw()
javascriptfunction* gen() {
try {
yield 1;
yield 2;
yield 3;
} finally {
console.log('cleanup');
}
}
const g = gen();
console.log(g.next()); // ?
console.log(g.return(99)); // ?
console.log(g.next()); // ?Output:
{ value: 1, done: false }
cleanup
{ value: 99, done: true }
{ value: undefined, done: true }Why: g.return(99) forces the generator to finish — it jumps to the finally block (if any), then returns { value: 99, done: true }. After that, the generator is done — all subsequent next() calls return { value: undefined, done: true }. Similarly, g.throw(err) throws an error inside the generator at the point of the last yield.