Practice Questions

1. With an example, explain why JavaScript developers prefer strict equality (===) over abstract equality (==).

Solution

Abstract equality (==) performs type coercion before comparing values, which can lead to unexpected results. Strict equality (===) compares both value and type without conversion.

// Abstract equality surprises
console.log("2" == 2);  // true (string converted to number)
console.log(0 == false);  // true (false converted to 0)
console.log("" == false);  // true (both converted to 0)

// Strict equality is predictable
console.log("2" === 2);  // false (different types)
console.log(0 === false);  // false (different types)

Strict equality makes code behavior more predictable and reduces bugs from unintended type coercion.

2. When comparing two objects with ===, JavaScript checks reference equality, not value equality. Explain what this means and provide an example demonstrating the difference.

Solution

Reference equality means two object variables are equal only if they point to the exact same object in memory. Two objects with identical properties and values are not equal if they are different objects.

const obj1 = { name: "Alice" };
const obj2 = { name: "Alice" };
const obj3 = obj1;

console.log(obj1 === obj2);  // false (different objects in memory)
console.log(obj1 === obj3);  // true (same reference)

For deep value equality, you need a library like Lodash (_.isEqual) or fast-deep-equal.

3. A student suggests using value == null to check if a variable is either null or undefined. Is this a reasonable use of abstract equality? Justify your answer.

Solution

Yes, this is one of the few acceptable uses of abstract equality. The expression value == null returns true for both null and undefined, but false for other falsy values like 0, "", or false.

let x;
console.log(x == null);  // true (undefined)
console.log(null == null);  // true
console.log(0 == null);  // false
console.log("" == null);  // false
console.log(false == null);  // false

This is more concise than value === null || value === undefined and is a well-known JavaScript idiom.

4. Provide any two interesting facts about how NaN behaves with equality operators.

Solution

Any two of the following:

  1. NaN is not equal to itself: NaN === NaN returns false (also true for ==)
  2. Because of this, JavaScript’s equality operators are not true equivalence relations (they are not reflexive)
  3. To check if a value is NaN, use Number.isNaN(value) or Object.is(value, NaN)
  4. Object.is(NaN, NaN) returns true, unlike ===
console.log(NaN === NaN);  // false
console.log(Number.isNaN(NaN));  // true
console.log(Object.is(NaN, NaN));  // true

5. How does JavaScript compare strings with comparison operators like < and >? Write code that demonstrates comparing two strings, and explain the result.

Solution

Strings are compared lexicographically (dictionary order) based on Unicode values, character by character:

console.log("apple" < "banana");  // true ("a" comes before "b")
console.log("Hello" < "Hi");      // true ("Hell" vs "Hi": "e" < "i")
console.log("abc" < "abcd");      // true (shorter string is "less" when prefix matches)
console.log("Z" < "a");           // true (uppercase letters have lower Unicode values)

JavaScript compares the two strings character by character from left to right. The first character that differs decides the result. If one string is a prefix of the other, the shorter string is considered “less than.”

6. With an example, explain how JavaScript’s logical operators (&& and ||) differ from boolean-only logical operators in other languages.

Solution

In JavaScript, && and || return one of their operands rather than a boolean. They use short-circuit evaluation:

  • x && y: Returns x if x is falsy; otherwise returns y
  • x || y: Returns x if x is truthy; otherwise returns y
console.log("hello" && "world");  // "world" (first is truthy, return second)
console.log(null && "world");  // null (first is falsy, return it)

console.log("hello" || "world");  // "hello" (first is truthy, return it)
console.log(null || "world");  // "world" (first is falsy, return second)

This allows patterns like DEBUG && console.log("msg") or name || "Anonymous".

7. List all the falsy values in JavaScript. Then explain why if (user) { ... } is a common pattern for checking if a variable has a meaningful value.

Solution

The falsy values in JavaScript are:

  • false
  • 0 (and -0)
  • "" (empty string)
  • null
  • undefined
  • NaN

The pattern if (user) { ... } works because:

  • If user is undefined (never assigned), the block is skipped
  • If user is null (explicitly no value), the block is skipped
  • If user is a valid object, string, or non-zero number, the block executes
let user;
if (user) {
  console.log("Has user");
}  // skipped (undefined is falsy)

user = { name: "Alice" };
if (user) {
  console.log("Has user");
}  // prints (objects are truthy)

8. Rewrite the following code using the ternary operator:

let message;
if (score >= 60) {
  message = "Pass";
} else {
  message = "Fail";
}
Solution
let message = score >= 60 ? "Pass" : "Fail";

The ternary operator condition ? valueIfTrue : valueIfFalse works well for simple conditional assignments like this one.

9. When would you choose a switch statement over an if-else-if chain? What important behavior should you remember about switch in JavaScript?

Solution

Use switch when:

  • Comparing a single value against multiple possible matches
  • The comparisons are all equality checks against constants
  • You want clearer, more readable code for many cases

Important behaviors to remember:

  1. JavaScript uses strict equality (===) for case matching
  2. Without break, execution “falls through” to subsequent cases
  3. Fall-through can be intentional for grouping cases
switch (day) {
  case "Saturday":
  case "Sunday":
    type = "Weekend";  // fall-through groups Sat and Sun
    break;
  default:
    type = "Weekday";
}

10. Write a function that takes a numeric grade (0-100) and returns the letter grade using the following scale: A (85+), B (70-84), C (55-69), F (below 55). Use an if-else-if chain.

Solution
function getLetterGrade(score) {
  if (score >= 85) {
    return "A";
  } else if (score >= 70) {
    return "B";
  } else if (score >= 55) {
    return "C";
  } else {
    return "F";
  }
}

console.log(getLetterGrade(92)); // "A"
console.log(getLetterGrade(75)); // "B"
console.log(getLetterGrade(60)); // "C"
console.log(getLetterGrade(45)); // "F"

The if-else-if chain stops at the first condition that is true, so the conditions have to be ordered from highest to lowest (or from lowest to highest) for the chain to give the right answer.

11. If you want to iterate over the values of an array, will you use for...of or for...in? Justify your decision.

Solution

Use for...of for iterating over array values.

for...of iterates over the values of iterable objects:

const arr = [10, 20, 30];
for (const value of arr) {
  console.log(value); // 10, 20, 30
}

for...in iterates over keys (indices for arrays) and has issues:

  • Iterates over all enumerable properties, not just indices
  • Includes inherited properties
  • Does not guarantee order
const arr = [10, 20, 30];
for (const key in arr) {
  console.log(key); // "0", "1", "2" (strings, not numbers)
}

12. Explain the difference between break and continue in a loop. Provide a practical example where continue is useful.

Solution
  • break exits the loop entirely
  • continue skips the rest of the current iteration and moves to the next one

A practical use of continue is skipping invalid or missing data:

const data = [10, undefined, 30, null, 50];
let sum = 0;

for (const value of data) {
  if (value == null) {
    continue;  // skip undefined and null
  }
  sum += value;
}

console.log(sum); // 90

Without continue, you would need to nest the accumulation logic inside an if block.

13. Write a for loop that calculates the sum of all numbers from 1 to n (inclusive), where n is a positive integer. Then rewrite it using a while loop.

Solution
// Using a for loop
let n = 10;
let sum = 0;
for (let i = 1; i <= n; i++) {
  sum += i;
}
console.log(sum); // 55

// Using a while loop
n = 10;
sum = 0;
let i = 1;
while (i <= n) {
  sum += i;
  i++;
}
console.log(sum); // 55

Both versions work. The for loop is more compact when you know the number of iterations in advance.