Episode 1 — Fundamentals / 1.18 — Operators and Type System
Interview Questions: Operators and the Type System
Model answers for == vs ===, type coercion, truthy/falsy, typeof, nullish coalescing, logical operators, and operator precedence.
How to use this material (instructions)
- Read lessons in order —
README.md, then1.18.a→1.18.g. - Practice out loud — definition → example → pitfall.
- Pair with exercises —
1.18-Exercise-Questions.md. - Quick review —
1.18-Quick-Revision.md.
Beginner (Q1–Q5)
Q1. What is the difference between == and ===?
Why interviewers ask: Fundamental JavaScript knowledge — reveals whether you understand type coercion.
Model answer:
=== (strict equality) compares both type and value without any conversion. 5 === "5" is false because one is a number and the other is a string. == (loose equality) performs type coercion before comparing: it converts operands to a common type using the abstract equality algorithm. 5 == "5" is true because "5" is converted to 5. The rule is: always use === as your default. The only commonly accepted use of == is value == null, which conveniently checks for both null and undefined.
Q2. What are falsy values in JavaScript?
Why interviewers ask: Tests awareness of implicit boolean conversion — a source of many bugs.
Model answer:
There are exactly 8 falsy values: false, 0, -0, 0n (BigInt zero), "" (empty string), null, undefined, and NaN. Every other value is truthy, including empty arrays [], empty objects {}, the string "0", and the string "false". This matters because if, while, &&, ||, and ternary operators all convert their operands to booleans. A common bug: using if (!value) when 0 or "" are valid inputs — they would be treated as missing.
Q3. What does typeof return for different values?
Why interviewers ask: Tests knowledge of JavaScript's type system and its quirks.
Model answer:
typeof returns a string: "number" (including NaN and Infinity), "string", "boolean", "undefined", "object" (for objects, arrays, and null), "function", "symbol", and "bigint". The most important quirk is typeof null === "object" — a historical bug from 1995 that cannot be fixed without breaking the web. For arrays, typeof returns "object", so use Array.isArray() instead. For null, use an explicit value === null check.
Q4. What is the difference between || and ???
Why interviewers ask: Tests knowledge of modern JavaScript (ES2020) and nuanced default-value logic.
Model answer:
|| (logical OR) returns the first truthy operand. It falls back for all falsy values: 0, "", false, null, undefined, NaN. ?? (nullish coalescing) returns the right operand only if the left is null or undefined — it preserves 0, "", and false as valid values. Example: 0 || 42 gives 42 (0 is falsy), but 0 ?? 42 gives 0 (0 is not null/undefined). Use ?? when 0, empty strings, or false are legitimate values — such as a user setting volume to 0.
Q5. Explain type coercion with the + operator.
Why interviewers ask: The + operator is the most common source of coercion bugs.
Model answer:
The + operator is overloaded: if either operand is a string, it converts the other to a string and concatenates. Otherwise, it performs numeric addition. This is why "5" + 3 gives "53" (string concatenation) while "5" - 3 gives 2 (numeric subtraction — only + has the string behavior). Evaluation is left-to-right, so 1 + 2 + "3" gives "33" (numeric 3 + string "3"), but "1" + 2 + 3 gives "123" (all concatenation after the first string). Best practice: always explicitly convert types before using + with mixed types.
Intermediate (Q6–Q10)
Q6. What is NaN and how do you check for it?
Why interviewers ask: NaN is unique in JavaScript — it breaks normal equality rules.
Model answer:
NaN (Not-a-Number) is the result of invalid arithmetic like 0/0, "hello" * 2, or undefined + 1. Despite its name, typeof NaN is "number". Its most surprising property: NaN !== NaN — it is the only value in JavaScript not equal to itself. To check for NaN, use Number.isNaN(value) which returns true only if the value is actually NaN without coercion. Avoid the global isNaN() function because it coerces its argument first — isNaN("hello") returns true even though "hello" is a string, not NaN. Alternatively, Object.is(NaN, NaN) returns true.
Q7. Explain short-circuit evaluation in && and ||.
Why interviewers ask: Tests understanding of how logical operators actually work in JavaScript — they do not always return booleans.
Model answer:
Short-circuit evaluation means the right operand is not evaluated if the result can be determined from the left operand alone. && stops at the first falsy value (returns it); if all are truthy, it returns the last value. || stops at the first truthy value (returns it); if all are falsy, it returns the last value. Crucially, they return the actual operand value, not a boolean. "hello" && 42 returns 42, not true. This enables patterns like callback && callback() (guard) and name || "Guest" (default). Short-circuiting also means side effects in the right operand may never execute: false && doSomething() — doSomething is never called.
Q8. Why is typeof null === "object" and how do you work around it?
Why interviewers ask: Classic JavaScript trivia that also tests practical problem-solving.
Model answer:
In the original JavaScript engine (1995), values were stored with a type tag. Objects had tag 0, and null was represented as the null pointer (0x00), which also had tag 0. So typeof checked the tag and incorrectly classified null as "object". A fix was proposed for ES2015 but rejected because it would break millions of existing websites. Workarounds: For null specifically, use value === null. For a "real object" check, use value !== null && typeof value === "object". For comprehensive type checking, use Object.prototype.toString.call(value) which returns "[object Null]" for null.
Q9. What are the logical assignment operators (&&=, ||=, ??=)?
Why interviewers ask: Tests knowledge of ES2021 features and their practical applications.
Model answer:
These operators combine a logical check with assignment. x ||= y assigns y to x only if x is currently falsy — equivalent to x || (x = y). x &&= y assigns y only if x is truthy — useful for transforming existing values like str &&= str.trim(). x ??= y assigns only if x is null or undefined — the safest for defaults because it preserves 0, "", and false. Key use case: options.timeout ??= 5000 — sets a default without overwriting intentional zero values. Note: the assignment only happens if the condition is met, which can matter for setter side effects or readonly properties.
Q10. Explain why [] == false is true but [] ? "yes" : "no" gives "yes".
Why interviewers ask: This is a flagship JavaScript gotcha that tests deep coercion understanding.
Model answer:
These use different coercion pathways. The ternary [] ? "yes" : "no" converts [] to a boolean: all objects (including empty arrays) are truthy, so it evaluates to "yes". But [] == false uses the abstract equality algorithm: (1) convert false to 0, (2) convert [] to a primitive via toString() which gives "", (3) convert "" to 0, (4) 0 == 0 is true. So the same value [] is "truthy" in a boolean context but "equal to false" under ==. This is exactly why === should be the default — it would return false because an array is not the boolean false.
Advanced (Q11–Q15)
Q11. Explain ToPrimitive and how [] + {} works.
Why interviewers ask: Tests deep understanding of the ECMAScript specification's type conversion mechanics.
Model answer:
When an object needs to become a primitive (for operators like +), JavaScript invokes the internal ToPrimitive operation. It first checks for [Symbol.toPrimitive](), then valueOf(), then toString(). For arrays, valueOf() returns the array itself (not a primitive), so toString() is called: [].toString() returns "". For plain objects, valueOf() also returns the object itself, so toString() is called: ({}).toString() returns "[object Object]". So [] + {} becomes "" + "[object Object]" which concatenates to "[object Object]". Related: [] + [] is "" + "" = "", and [] - [] is 0 - 0 = 0 (because - forces numeric conversion, and Number("") is 0).
Q12. What is Object.is() and when would you use it over ===?
Why interviewers ask: Tests knowledge of edge cases in JavaScript equality.
Model answer:
Object.is(a, b) works like === except for two edge cases: Object.is(NaN, NaN) returns true (while NaN === NaN is false), and Object.is(0, -0) returns false (while 0 === -0 is true). In practice, you rarely need Object.is() in application code. It is used internally by Array.prototype.includes() (which can find NaN in an array, unlike indexOf), by React's Object.is-based comparison in useState and useMemo to detect state changes, and in polyfills or library code that needs mathematically precise equality. For everyday comparisons, === is still the right choice.
Q13. How does string comparison with <, > work, and what are the pitfalls?
Why interviewers ask: Tests understanding of Unicode-based comparison and common sorting bugs.
Model answer:
When both operands are strings, relational operators compare character by character using Unicode code points (UTF-16 values). "a" (97) > "A" (65) is true. "apple" < "banana" is true because "a" < "b". The major pitfall: numeric strings are compared lexicographically, not numerically. "10" < "9" is true because "1" (49) < "9" (57). This breaks sorting: ["10", "9", "2"].sort() gives ["10", "2", "9"]. Fix: convert to numbers first, or use a comparison function: arr.sort((a, b) => Number(a) - Number(b)). When one operand is a string and the other a number, the string is converted to a number for comparison.
Q14. What are the differences between Number(), parseInt(), parseFloat(), and unary +?
Why interviewers ask: Tests practical knowledge of type conversion in real codebases.
Model answer:
Number(x) converts the entire value strictly: Number("42px") is NaN, Number("") is 0, Number(null) is 0. parseInt(str, radix) parses from the start of a string, stopping at the first non-numeric character: parseInt("42px") is 42. It ignores leading whitespace and requires a radix parameter (usually 10) for safety. parseFloat(str) is similar but handles decimals: parseFloat("3.14em") is 3.14. Unary + is equivalent to Number(): +"42" is 42, +"" is 0. Choose Number() for strict full-value conversion, parseInt()/parseFloat() for CSS-like values with units, and unary + as a shorthand when brevity is preferred and the team convention allows it.
Q15. How does the == algorithm handle null and undefined, and why is == null sometimes acceptable?
Why interviewers ask: Tests nuanced understanding of the one legitimate use case for ==.
Model answer:
The abstract equality algorithm has a special rule: null == undefined is true, and null/undefined are not coerced to any other type for ==. So null == 0 is false, null == "" is false, undefined == false is false. This means value == null is exactly equivalent to value === null || value === undefined — it matches only those two values and nothing else. Many style guides (including those from Airbnb and some ESLint configs with the eqeqeq rule set to "smart") allow this specific use because the semantics are clear and useful: checking "is this value absent?" without writing out both comparisons. It is the one case where == is arguably more readable than ===.
Quick-fire
| # | Question | One-line answer |
|---|---|---|
| 1 | typeof NaN | "number" |
| 2 | typeof null | "object" (bug) |
| 3 | typeof [] | "object" — use Array.isArray() |
| 4 | NaN === NaN | false |
| 5 | null == undefined | true |
| 6 | null === undefined | false |
| 7 | "" == false | true |
| 8 | 0 == false | true |
| 9 | [] == false | true |
| 10 | Boolean([]) | true |
| 11 | "5" + 3 | "53" |
| 12 | "5" - 3 | 2 |
| 13 | Object.is(NaN, NaN) | true |
| 14 | Object.is(0, -0) | false |
| 15 | [] + [] | "" |
| 16 | [] + {} | "[object Object]" |
| 17 | Number("") | 0 |
| 18 | Number(null) | 0 |
| 19 | Number(undefined) | NaN |
| 20 | 0 ?? 42 | 0 |