Chapter 13: The Interview Gauntlet - Common Tricks and Gotchas
· 12 min read
Chapter 13: The Interview Gauntlet - Common Tricks and Gotchas
Type Coercion Mysteries
// The infamous equality comparisons
console.log([] == ![]); // true (wat?!)
// Explanation:
// [] == ![]
// [] == false (! converts [] to false)
// "" == false ([] converts to "")
// 0 == false ("" converts to 0)
// true (0 == 0)
console.log([] + []); // "" (empty string)
console.log({} + []); // 0 (in some contexts)
console.log([] + {}); // "[object Object]"
console.log({} + {}); // "[object Object][object Object]"
// Truthy/falsy gotchas
console.log(Boolean([])); // true (empty array is truthy!)
console.log(Boolean({})); // true (empty object is truthy!)
console.log(Boolean("")); // false
console.log(Boolean("0")); // true (string "0" is truthy!)
console.log(Boolean(0)); // false
console.log(Boolean(NaN)); // false
// typeof surprises
console.log(typeof null); // "object" (historical bug)
console.log(typeof undefined); // "undefined"
console.log(typeof []); // "object"
console.log(typeof function(){}); // "function"
console.log(typeof NaN); // "number"
The this Binding Puzzle
// Global context
function globalFunction() {
console.log(this); // Window (browser) or global (Node.js) in non-strict mode
}
// Object method
const obj = {
name: "Alice",
greet: function() {
console.log(this.name); // "Alice"
// Nested function loses context
function inner() {
console.log(this.name); // undefined (this is global/undefined)
}
inner();
// Arrow function preserves context
const arrowInner = () => {
console.log(this.name); // "Alice"
};
arrowInner();
}
};
// Call, apply, bind
function introduce(greeting, punctuation) {
return `${greeting}, I'm ${this.name}${punctuation}`;
}
const person = { name: "Bob" };
console.log(introduce.call(person, "Hello", "!")); // "Hello, I'm Bob!"
console.log(introduce.apply(person, ["Hi", "."])); // "Hi, I'm Bob."
const boundIntroduce = introduce.bind(person, "Hey");
console.log(boundIntroduce("?")); // "Hey, I'm Bob?"
Event Loop and Microtask Queue
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
Promise.resolve().then(() => {
console.log("4");
setTimeout(() => console.log("5"), 0);
});
setTimeout(() => console.log("6"), 0);
console.log("7");
// Output: 1, 7, 3, 4, 2, 6, 5
// Explanation:
// 1. Synchronous code runs first: 1, 7
// 2. Microtasks (Promises) run next: 3, 4
// 3. Macrotasks (setTimeout) run last: 2, 6, 5
Variable Hoisting Edge Cases
// Function vs variable hoisting precedence
console.log(typeof foo); // "function"
var foo = "I'm a variable";
function foo() {
return "I'm a function";
}
console.log(typeof foo); // "string"
// Explanation: Function declarations are hoisted before variable declarations
// But variable assignments happen in order
// Temporal Dead Zone gotcha
function tdz() {
console.log(typeof x); // "undefined" - x doesn't exist yet
if (true) {
console.log(typeof x); // ReferenceError - x is in TDZ here
let x = "I'm block scoped";
}
}
Closure Memory Leaks
// Memory leak example
function createLeak() {
const hugeArray = new Array(1000000).fill("data");
return function() {
// This closure keeps the entire hugeArray in memory
// even though it doesn't use it
return "I'm a small function";
};
}
// Better approach
function createNoLeak() {
const hugeArray = new Array(1000000).fill("data");
const summary = hugeArray.length; // Extract what you need
return function() {
return `Array had ${summary} elements`;
};
}
Classic Interview Questions
Question 1: What’s the output?
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000);
}
// Output: 3, 3, 3 (after 1 second)
// Why: var is function-scoped, closures capture the same variable
// Fixed versions:
// With let
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000);
}
// Output: 0, 1, 2
// With IIFE
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(() => console.log(j), 1000);
})(i);
}
// Output: 0, 1, 2
Question 2: Object property access
const obj = {
a: 1,
b: 2,
c: {
a: 3,
b: 4
}
};
const { a, b, c: { a: nestedA } } = obj;
console.log(a, b, nestedA); // 1, 2, 3
// What happens here?
const key = 'a';
console.log(obj.key); // undefined (looks for literal 'key' property)
console.log(obj[key]); // 1 (evaluates variable)
Question 3: Function behavior
function test() {
console.log(arguments.length);
console.log(typeof arguments);
console.log(Array.isArray(arguments));
}
test(1, 2, 3, 4, 5);
// Output: 5, "object", false
// arguments is array-like but not a real array
// Modern equivalent with rest parameters
function modernTest(...args) {
console.log(args.length);
console.log(typeof args);
console.log(Array.isArray(args));
}
modernTest(1, 2, 3, 4, 5);
// Output: 5, "object", true
Question 4: Scope and closures
let x = 1;
function outer() {
let x = 2;
function inner() {
console.log(x); // What does this log?
}
return inner;
}
const fn = outer();
fn(); // Logs: 2 (lexical scope, not dynamic scope)
// Even after outer() finished, inner() remembers its scope
Tricky Array Methods
// map vs forEach return behavior
const numbers = [1, 2, 3];
// forEach returns undefined
const result1 = numbers.forEach(n => n * 2);
console.log(result1); // undefined
// map returns new array
const result2 = numbers.map(n => n * 2);
console.log(result2); // [2, 4, 6]
// sort() mutates original array and returns it
const arr = [3, 1, 4, 1, 5];
const sorted = arr.sort();
console.log(arr); // [1, 1, 3, 4, 5] - mutated!
console.log(sorted); // [1, 1, 3, 4, 5] - same reference
// Gotcha: sort() converts to strings by default
const nums = [1, 10, 2, 20];
nums.sort();
console.log(nums); // [1, 10, 2, 20] - not [1, 2, 10, 20]!
// Fix with compare function
nums.sort((a, b) => a - b);
console.log(nums); // [1, 2, 10, 20]
Object Reference Gotchas
const a = { value: 1 };
const b = a;
const c = { value: 1 };
console.log(a === b); // true (same reference)
console.log(a === c); // false (different references)
console.log(a.value === c.value); // true (same values)
// Mutation through references
b.value = 2;
console.log(a.value); // 2 (a and b point to same object)
// Object.assign creates shallow copy
const original = { a: 1, nested: { b: 2 } };
const copy = Object.assign({}, original);
copy.a = 10;
copy.nested.b = 20;
console.log(original.a); // 1 (not affected)
console.log(original.nested.b); // 20 (affected! shallow copy)
Promise Chain Gotchas
// What gets logged?
Promise.resolve(1)
.then(val => {
console.log(val); // 1
return val + 1;
})
.then(val => {
console.log(val); // 2
// No return statement!
})
.then(val => {
console.log(val); // undefined
});
// Error handling gotcha
Promise.reject("error")
.then(val => console.log("Success:", val))
.catch(err => {
console.log("Caught:", err); // "Caught: error"
return "recovered";
})
.then(val => {
console.log("After catch:", val); // "After catch: recovered"
});
Advanced Gotchas
parseInt surprises
console.log(parseInt("08")); // 8
console.log(parseInt("08", 10)); // 8
console.log(parseInt("08", 8)); // 0 (invalid octal)
["1", "2", "3"].map(parseInt);
// What do you think this returns?
// [1, NaN, NaN]
// Because: parseInt(string, radix)
// "1", 0 -> parseInt("1", 0) -> 1
// "2", 1 -> parseInt("2", 1) -> NaN (radix 1 is invalid)
// "3", 2 -> parseInt("3", 2) -> NaN (3 is not valid in base 2)
// Fix:
["1", "2", "3"].map(str => parseInt(str, 10)); // [1, 2, 3]
Date constructor gotchas
// Month is 0-indexed!
console.log(new Date(2023, 1, 15)); // February 15, 2023 (not January!)
// Different behavior for Date.parse vs constructor
console.log(new Date("2023-01-15")); // Local timezone
console.log(new Date("2023-01-15T00:00:00")); // Local timezone
console.log(new Date("2023-01-15T00:00:00Z")); // UTC
Number precision issues
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false
// Fix with proper comparison
function isEqual(a, b, tolerance = Number.EPSILON) {
return Math.abs(a - b) < tolerance;
}
console.log(isEqual(0.1 + 0.2, 0.3)); // true
Next Chapter: Chapter 14: Modern JavaScript Features (ES6+)
Previous Chapter: Chapter 12: Advanced Concepts and Modern Features
Table of Contents: JavaScript Guide