Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

JavaScript (ES2026)  Module 3 – Control flow

Conditionals: if, else and the ternary operator

Write JavaScript if and else chains that cover every case, choose a value with the ternary operator, and turn deep nesting into flat guard clauses.

  • Beginner
  • 16 minutes
  • Examples run with Node.js 24.21.0 and quickjs 0.32.0
  • By MySmartCoPilot
Free lesson, Pro answers: 16 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Write if/else chains that handle every case
  • Use the conditional (ternary) operator for simple value choices
  • Replace deep nesting with guard clauses
  • Fix conditions that ignore the data, such as x = 0 and x === 'a' || 'b'

Before you start

What you’ll learn · Before you start

What you will learn

  • Write if/else chains that handle every case
  • Use the conditional (ternary) operator for simple value choices
  • Replace deep nesting with guard clauses
  • Fix conditions that ignore the data, such as x = 0 and x === 'a' || 'b'

Before you start

On this page

Every program has to choose sooner or later: book the seat or join the waiting list, charge one fee or another, show an error or carry on. In JavaScript, if (condition) { … } executes its block when the condition holds and skips it otherwise, and an else block attached to it covers the opposite case. Chaining else if checks several conditions in turn, and only the first one that holds gets its block executed. To pick between two values inside an expression, there is the conditional operator, condition ? a : b, called the ternary operator because it takes three operands. This lesson writes each form, shows the mistakes that make a condition give the same answer whatever the data says, and turns deeply nested checks into flat guard clauses.

if and else

An if statement is the keyword if, a condition in parentheses and the statement to run when the condition holds. That statement is almost always a block, a group of statements between braces. An else after it adds the statement for the other case.

function and return

Each decision in the examples sits inside a function so that one program can try it on several inputs. function book(seatsLeft) { … } names the code and its parameter, book(2) calls it with seatsLeft equal to 2, and return sends a value back to the caller and stops the function on that line. The functions module, later in the track, explains all of this properly.

An if with an else JavaScript · seat-check.mjs
// One decision with two paths: book a seat while any are left, otherwise join the waiting list.
function book(seatsLeft) {
  if (seatsLeft > 0) {
    console.log(`Booked. Seats remaining after this one: ${seatsLeft - 1}`);
  } else {
    console.log('Full: added to the waiting list');
  }
  console.log('(this line runs on both paths)');
}

book(2);
book(0);

Output

Booked. Seats remaining after this one: 1
(this line runs on both paths)
Full: added to the waiting list
(this line runs on both paths)

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node seat-check.mjs

With 2 seats remaining, seatsLeft > 0 is true, so the first block runs and the else block is skipped; with 0 seats it is the other way round. The last line of the function sits outside both blocks, so it runs on either path. Press Run, then Edit, and add book(-1) to see which path a negative number takes.

Always write the braces

The braces are optional when a block would hold a single statement, and leaving them out causes two classic bugs:

An if without braces controls one statement JavaScript · missing-braces.mjs
// Without braces, an if controls exactly one statement. Indentation means nothing to JavaScript.
const balance = 900;
const price = 800;

if (balance < price)
  console.log('Not enough balance');
  console.log('Payment refused'); // looks like part of the if, but runs every time

if (balance >= price) {
  console.log('Payment accepted');
}

// An else belongs to the nearest if without an else, whatever the indentation says.
const signedIn = false;
const isAdmin = false;
if (signedIn)
  if (isAdmin) console.log('Admin tools');
else console.log('Please sign in'); // never printed: this else belongs to if (isAdmin)

console.log('end of program');

Output

Payment refused
Payment accepted
end of program

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node missing-braces.mjs

The balance is large enough, yet the program prints “Payment refused” before “Payment accepted”. Without braces, the if on line 5 controls only the next statement. The indented console.log('Payment refused') after it is an ordinary statement that runs every time, because JavaScript ignores indentation completely.

The second bug is the dangling else. The language specification settles it with one rule: an else belongs to the nearest if that does not have an else yet. So else console.log('Please sign in') belongs to if (isAdmin), not to if (signedIn), and a visitor who is not signed in sees nothing at all. Braces around every block prevent both bugs, and ESLint’s curly rule reports each body written without them, both bodies of this program included.

What a condition accepts

The condition does not have to be true or false. JavaScript converts whatever value it gets with the same rules as Boolean(value): false, 0, -0, 0n, NaN, the empty string '', null and undefined count as false, and every other value counts as true, including '0', a string holding one space, and every array and object, even empty ones. (Browsers keep one historical exception, the object document.all, which counts as false.) The lesson on booleans and truthiness goes through these values one by one. For conditions, the consequence is that a short test can ask a different question from the one you meant:

Testing what you mean JavaScript · truthy-conditions.mjs
// An if converts its condition to true or false first, so test what you actually mean.
const order = { items: [], quantity: 0, note: ' ' };

if (order.items) console.log('items: an empty array still counts as true');
if (order.items.length > 0) console.log('not printed: the order has no items');

if (!order.quantity) console.log('quantity: 0 counts as false, like a missing value');
if (order.quantity !== undefined) console.log('quantity was given, and it is 0');

if (order.note) console.log('note: a single space counts as true');
if (order.note.trim() !== '') console.log('not printed: the note is blank');

Output

items: an empty array still counts as true
quantity: 0 counts as false, like a missing value
quantity was given, and it is 0
note: a single space counts as true

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node truthy-conditions.mjs

if (order.items) asks “is there an items value?”, not “are there any items?”, so an empty array passes it. if (!order.quantity) treats a quantity of 0, entered on purpose, exactly like a missing one. Write the question you mean instead: items.length > 0, quantity !== undefined, note.trim() !== ''. The short form is fine when every falsy value really means “nothing here”, for example an optional name that is either a non-empty string or undefined.

Several cases with else if

Each else if adds one more condition to the chain. JavaScript checks the conditions in the order they are written: the first one that holds wins, its block runs, and the conditions after it are never evaluated. When none of them holds, control goes to the final else. Here a shop charges for delivery by distance, in three bands with made-up rates:

Delivery fees in three bands JavaScript · delivery-fee.mjs
// A delivery fee by distance in kilometres, with made-up rates. Exactly one block of the chain runs.
function deliveryFee(km) {
  let fee;
  if (km <= 3) {
    fee = 25;
  } else if (km <= 8) {
    fee = 40;
  } else if (km <= 15) {
    fee = 70;
  } else {
    fee = null; // too far: no delivery
  }
  return fee;
}

// Each limit, and a distance just past it.
console.log('1 km:', deliveryFee(1));
console.log('3 km:', deliveryFee(3));
console.log('3.1 km:', deliveryFee(3.1));
console.log('8 km:', deliveryFee(8));
console.log('8.5 km:', deliveryFee(8.5));
console.log('15 km:', deliveryFee(15));
console.log('15.2 km:', deliveryFee(15.2));

Output

1 km: 25
3 km: 25
3.1 km: 40
8 km: 40
8.5 km: 70
15 km: 70
15.2 km: null

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node delivery-fee.mjs

Flowchart of deliveryFee: km <= 3, then km <= 8, then km <= 15 are tested in turn; the first true one sets the fee.km <= 3?fee = 25km <= 8?fee = 40km <= 15?fee = 70else:fee = nulltruefalsetruefalsetruefalse

How the else if chain of delivery-fee.mjs picks exactly one block

Text description of the diagram

A flowchart of the chain in deliveryFee(km), read from the top.

  1. The first test is km <= 3. When it is true, fee becomes 25, and no other test runs.
  2. When it is false, the second test, km <= 8, runs. True sets fee to 40. It needs no lower limit, because only distances above 3 ever reach it.
  3. When that is false too, km <= 15 runs. True sets fee to 70.
  4. When all three tests are false, the else block sets fee to null: the distance is too far for a delivery.

Every path ends after exactly one block has run, and the function then returns fee, so every distance gets exactly one fee.

Read the chain the way JavaScript does. For 8.5 km, km <= 3 and km <= 8 are both false, so km <= 15 decides and the fee is 70. The second test needs no lower limit: it only ever runs after km <= 3 was false, so every distance that reaches it is already above 3. The program tries each limit and a value just past it, 3 and 3.1, 8 and 8.5, 15 and 15.2, because that is where chains go wrong: a < typed where <= was meant changes the answer for the limit itself and for nothing else. And because the chain ends with else, every distance gets an answer, even one that no band covers.

A common mistake: the widest test first

Swapping the tests around changes the answers. Here the widest limit comes first:

The same tests, widest first JavaScript · wrong-order.mjs
// The same three limits with the widest test first: it is true for every distance up to 15 km.
function deliveryFee(km) {
  let fee;
  if (km <= 15) {
    fee = 70;
  } else if (km <= 8) {
    fee = 40; // can never run: a distance of 8 km or less already matched km <= 15
  } else if (km <= 3) {
    fee = 25; // can never run either
  } else {
    fee = null;
  }
  return fee;
}

console.log('1 km:', deliveryFee(1));
console.log('8 km:', deliveryFee(8));
console.log('16 km:', deliveryFee(16));

Output

1 km: 70
8 km: 70
16 km: null

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node wrong-order.mjs

A 1 km delivery now costs 70. km <= 15 is true for it, so the first block runs and the cheaper bands are never reached. There is no error and no warning: the two else if blocks have simply become code that can never run, and the linter’s rules do not notice. Put the smallest band first and work outwards, or write each band with both of its ends (km > 3 && km <= 8) so that the order stops mattering; either way, test every limit.

Two conditions that ignore the data

Some conditions look right but give the same answer whatever the data. Both of these are common in real code, and both are easy to catch:

An assignment and an or that ignore the data JavaScript · condition-bugs.mjs
// Bug 1: = assigns and === compares. The assignment's value, 0, becomes the condition.
let attempts = 3;
if (attempts = 0) {
  console.log('Locked out');
} else {
  console.log(`attempts is now ${attempts}: the test changed it`);
}

// Bug 2: each side of || is tested on its own, and a non-empty string counts as true.
const status = 'cancelled';
if (status === 'paid' || 'refunded') {
  console.log(`Treated as settled: ${status}`);
}

// The fixes: compare the variable each time, or ask whether a list includes it.
if (status === 'paid' || status === 'refunded') console.log('not printed');
if (['paid', 'refunded'].includes(status)) console.log('not printed either');
console.log('end of program');

Output

attempts is now 0: the test changed it
Treated as settled: cancelled
end of program

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node condition-bugs.mjs

In the first test, attempts = 0 is an assignment, not a comparison. It stores 0 in attempts, and its value, 0, becomes the condition, so the else block runs and shows that the test itself changed the variable. The answer depends only on the value assigned: attempts = 5 would make the condition 5, which counts as true, and lock everyone out. Compare with ===, which checks whether two values are the same without converting either of them; the lesson on equality explains why code avoids ==.

In the second, status === 'paid' || 'refunded' means (status === 'paid') || 'refunded', because === binds more tightly than ||. The comparison is false, so || gives back its right-hand side, the string 'refunded', which is not empty and therefore counts as true: every order is treated as settled. Write the comparison out each time, status === 'paid' || status === 'refunded', or ask whether a list contains the value, ['paid', 'refunded'].includes(status), which stays short as the list grows.

ESLint’s recommended rules report both mistakes. no-cond-assign flags the assignment with “Expected a conditional expression and instead saw an assignment”, and no-constant-condition flags both conditions with “Unexpected constant condition”. The wrong-order chain above, by contrast, passes every rule, which is why the boundary values belong in your tests.

JavaScript Linter Paste condition-bugs.mjs and the Recommended rules point at both constant conditions; the Strict rules add curly for the two bodies without braces in missing-braces.mjs.

Choosing a value with the ternary operator

condition ? valueIfTrue : valueIfFalse is an expression, so it can stand wherever a value can: inside a template literal, after return, on the right of = or as an argument. JavaScript evaluates the condition, then only the side it chose:

The conditional operator JavaScript · ternary.mjs
// condition ? valueIfTrue : valueIfFalse is an expression: it produces a value you can store or print.
const count = 1;
console.log(`${count} ${count === 1 ? 'item' : 'items'} in the basket`);

// Only the side that is chosen runs, so the other side may hold code that would fail.
const user = null;
const name = user !== null ? user.name : 'guest'; // user.name is never read
console.log(`Hello, ${name}`);

// A chain of ternaries reads like an else if chain when each test starts a new line.
function grade(marks) {
  return marks >= 90 ? 'A'
    : marks >= 75 ? 'B'
    : marks >= 50 ? 'C'
    : 'F';
}
console.log(grade(93), grade(75), grade(74), grade(12));

Output

1 item in the basket
Hello, guest
A B C F

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node ternary.mjs

Reading user.name would throw a TypeError while user is null, but it never happens, because the condition is false and that side is not evaluated. The third operand may itself be a ternary: a ? x : b ? y : z groups as a ? x : (b ? y : z), so the tests still run in order from left to right, and a chain laid out one test per line, as in grade, reads like an else if chain. Keep ternaries for choosing between values. When each branch has to do something, when there are more than three cases, or when one ternary would end up inside another’s middle operand, an if statement says it more clearly. Teams that want no chains at all turn on ESLint’s no-nested-ternary rule.

Flatter code with guard clauses

Every condition you nest pushes the real work further to the right and further away from the test it depends on. The two versions below apply the same three rules for a discount coupon and print identical results, but they read very differently:

Nested conditions and guard clauses

Nested · coupon-nested.mjs

// Each rule adds a level of nesting, and every else sits far below the test it answers.
function couponProblem(cart, coupon) {
  if (cart.items.length > 0) {
    if (!coupon.expired) {
      if (cart.total >= coupon.minTotal) {
        return null; // no problem: the coupon applies
      } else {
        return `add ${coupon.minTotal - cart.total} more to use ${coupon.code}`;
      }
    } else {
      return `${coupon.code} has expired`;
    }
  } else {
    return 'the cart is empty';
  }
}

const save50 = { code: 'SAVE50', minTotal: 500, expired: false };
const welcome = { code: 'WELCOME', minTotal: 300, expired: true };
console.log(couponProblem({ items: [], total: 0 }, save50) ?? 'coupon applies');
console.log(couponProblem({ items: ['lamp'], total: 450 }, save50) ?? 'coupon applies');
console.log(couponProblem({ items: ['lamp', 'rug'], total: 1200 }, save50) ?? 'coupon applies');
console.log(couponProblem({ items: ['rug'], total: 750 }, welcome) ?? 'coupon applies');

Output

the cart is empty
add 50 more to use SAVE50
coupon applies
WELCOME has expired

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node coupon-nested.mjs

Guard clauses · coupon-guards.mjs

// The same rules as guard clauses: each problem is checked and returned first; the normal case comes last.
function couponProblem(cart, coupon) {
  if (cart.items.length === 0) return 'the cart is empty';
  if (coupon.expired) return `${coupon.code} has expired`;
  if (cart.total < coupon.minTotal) return `add ${coupon.minTotal - cart.total} more to use ${coupon.code}`;
  return null; // no problem: the coupon applies
}

const save50 = { code: 'SAVE50', minTotal: 500, expired: false };
const welcome = { code: 'WELCOME', minTotal: 300, expired: true };
console.log(couponProblem({ items: [], total: 0 }, save50) ?? 'coupon applies');
console.log(couponProblem({ items: ['lamp'], total: 450 }, save50) ?? 'coupon applies');
console.log(couponProblem({ items: ['lamp', 'rug'], total: 1200 }, save50) ?? 'coupon applies');
console.log(couponProblem({ items: ['rug'], total: 750 }, welcome) ?? 'coupon applies');

Output

the cart is empty
add 50 more to use SAVE50
coupon applies
WELCOME has expired

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node coupon-guards.mjs

In the nested version, the message for an empty cart is 11 lines below the test that produces it. The second version deals with each problem first and returns at once. These early checks are called guard clauses. Because return leaves the function, a line after a guard runs only when the guard did not, so no else is needed, and the coupon that passes every check is handled on the last line, at the function’s outer level. Each rule is one line, so a fourth rule, say a coupon that only new customers may use, adds one line instead of one more level of nesting. The same idea works with throw for input that a function must refuse, as the exercise shows, and with continue and break inside loops, which the lesson on loops covers.

When a lookup beats an if chain

A long chain that compares one value with a list of fixed values is really a table written as code. You can store the table as data instead:

The same mapping as an if chain and as a Map JavaScript · lookup-vs-ifs.mjs
// The same fixed mapping twice: letter grades to points on a made-up 10-point scale.
function pointsWithIfs(grade) {
  let points;
  if (grade === 'A') points = 10;
  else if (grade === 'B') points = 8;
  else if (grade === 'C') points = 6;
  else if (grade === 'D') points = 4;
  else if (grade === 'E') points = 2;
  else points = 0;
  return points;
}

// As data: one entry per grade, and one lookup instead of up to five tests.
const POINTS = new Map([
  ['A', 10],
  ['B', 8],
  ['C', 6],
  ['D', 4],
  ['E', 2],
]);
function pointsWithMap(grade) {
  return POINTS.get(grade) ?? 0;
}

console.log('B:', pointsWithIfs('B'), pointsWithMap('B'));
console.log('E:', pointsWithIfs('E'), pointsWithMap('E'));
console.log('Z:', pointsWithIfs('Z'), pointsWithMap('Z'));
console.log('grades on the scale:', POINTS.size);

Output

B: 8 8
E: 2 2
Z: 0 0
grades on the scale: 5

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node lookup-vs-ifs.mjs

Both functions agree. The Map holds each grade and its points once, finds an answer with one get call where the chain may run five tests, can be counted (POINTS.size), listed or loaded from a file, and ?? 0 supplies the answer for a grade it does not know. The ECMAScript specification requires a Map to find a key faster than checking its entries one by one: on average, its access time must grow more slowly than the number of entries. A chain still wins when the tests are ranges, like the delivery bands, or combine several values. The next lesson adds the third way to choose, the switch statement, and compares all three.

JavaScript & TypeScript Online Compiler Write your own if, else if and ternary chains in a full editor and run them on this device.

Exercises

Exercise · Easy · JavaScript

Choose the fare category of a passenger

A city's buses charge by fare category. Write getFareCategory(age, isStudent), which returns one of the strings 'child', 'student', 'senior' and 'adult'.

  • Passengers under 12 are 'child', students included.
  • Passengers aged 60 or more are 'senior', students included.
  • Students aged 12 to 59 are 'student'.
  • Everyone else is 'adult'.
  • age must be a whole number of years, 0 or more. For anything else, such as -1, 2.5, NaN or the string '30', throw a RangeError before any other test. isStudent is always true or false.

So getFareCategory(11, true) is 'child', getFareCategory(12, true) is 'student', getFareCategory(12, false) is 'adult' and getFareCategory(60, true) is 'senior'.

Starter code · fares.mjs

/**
 * The fare category of a passenger (the rules are in the prompt).
 * @param {number} age a whole number of years, 0 or more
 * @param {boolean} isStudent
 * @returns {'child' | 'student' | 'senior' | 'adult'}
 */
export function getFareCategory(age, isStudent) {
  return 'adult';
}
The sample tests · fares.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { getFareCategory } from './fares.mjs';

test('passengers under 12 are children', () => {
  assert.equal(getFareCategory(0, false), 'child');
  assert.equal(getFareCategory(5, false), 'child');
  assert.equal(getFareCategory(11, false), 'child');
});

test('students under 12 are still children', () => {
  assert.equal(getFareCategory(8, true), 'child');
  assert.equal(getFareCategory(11, true), 'child');
});

test('passengers from 12 to 59 who are not students are adults', () => {
  assert.equal(getFareCategory(12, false), 'adult');
  assert.equal(getFareCategory(30, false), 'adult');
  assert.equal(getFareCategory(59, false), 'adult');
});

test('students from 12 to 59 are students', () => {
  assert.equal(getFareCategory(12, true), 'student');
  assert.equal(getFareCategory(21, true), 'student');
  assert.equal(getFareCategory(59, true), 'student');
});

test('passengers of 60 or more are seniors', () => {
  assert.equal(getFareCategory(60, false), 'senior');
  assert.equal(getFareCategory(84, false), 'senior');
});

test('students of 60 or more are seniors', () => {
  assert.equal(getFareCategory(60, true), 'senior');
  assert.equal(getFareCategory(67, true), 'senior');
});

test('negative and fractional ages throw a RangeError', () => {
  assert.throws(() => getFareCategory(-1, false), RangeError);
  assert.throws(() => getFareCategory(2.5, false), RangeError);
});

test('NaN and a string age throw a RangeError', () => {
  assert.throws(() => getFareCategory(NaN, true), RangeError);
  assert.throws(() => getFareCategory('30', false), RangeError);
});
A hint

Begin with one guard clause that throws for every bad age: Number.isInteger is false for 2.5, NaN and '30' alike. Then return early for the two age rules that ignore isStudent, so that by the time your code looks at isStudent, only passengers aged 12 to 59 are left.

The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Check yourself

5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 5 What does condition-bugs.mjs print?

    What does this program print? Choose one answer.

    // Bug 1: = assigns and === compares. The assignment's value, 0, becomes the condition.
    let attempts = 3;
    if (attempts = 0) {
      console.log('Locked out');
    } else {
      console.log(`attempts is now ${attempts}: the test changed it`);
    }
    
    // Bug 2: each side of || is tested on its own, and a non-empty string counts as true.
    const status = 'cancelled';
    if (status === 'paid' || 'refunded') {
      console.log(`Treated as settled: ${status}`);
    }
    
    // The fixes: compare the variable each time, or ask whether a list includes it.
    if (status === 'paid' || status === 'refunded') console.log('not printed');
    if (['paid', 'refunded'].includes(status)) console.log('not printed either');
    console.log('end of program');
    Show the answer to question 1

    Answer: it prints

    attempts is now 0: the test changed it
    Treated as settled: cancelled
    end of program

    attempts = 0 assigns 0 and gives back 0, which counts as false, so the else block runs and prints the new value. status === 'paid' || 'refunded' gives back the string 'refunded' when the comparison is false, and a non-empty string counts as true, so the second message is printed for a cancelled order.

  2. Question 2 of 5 What does this print?

    Read the code, then choose one answer.

    const temperature = 34;
    if (temperature > 20) {
      console.log('warm');
    } else if (temperature > 30) {
      console.log('hot');
    } else {
      console.log('cold');
    }
    Show the answer to question 2

    Answer: warm

    JavaScript runs the block of the first condition that is true and skips the rest of the chain. 34 > 20 is already true, so "hot" can never be printed by this chain: the wider test comes first. Testing temperature > 30 first fixes it.

  3. Question 3 of 5 For which of these values of value does if (value) { … } skip its block?

    Choose every answer that is right.

    Show the answer to question 3

    Answer:

    • 0
    • null
    • NaN

    0, null and NaN are falsy, so the block is skipped. The string '0' and the string ' ' are not empty, and every array counts as true, empty or not, so the block runs for those three.

  4. Question 4 of 5 What does this print?

    Read the code, then choose one answer.

    const hasTicket = false;
    const isVip = true;
    if (hasTicket)
      if (isVip) console.log('lounge');
    else console.log('ticket desk');
    console.log('done');
    Show the answer to question 4

    Answer: done

    The else belongs to the nearest if without an else, which is if (isVip), whatever the indentation suggests. hasTicket is false, so the whole inner if, else included, is skipped, and only "done" is printed. Braces around each block make the pairing explicit.

  5. Question 5 of 5 const label = user ? user.name : 'guest'; runs while user is null. What happens?

    Choose one answer.

    Show the answer to question 5

    Answer: label becomes 'guest'

    The conditional operator evaluates its condition, then only the side it chose. null is falsy, so user.name is never evaluated and label is 'guest'.

Interview questions

Fresher · What is the difference between an if statement and the conditional (ternary) operator in JavaScript?

An if statement chooses which statements run: each branch of if (condition) { … } else { … } can do anything, such as assign variables, call functions or return, and the statement itself produces no value. The conditional operator, condition ? a : b, is an expression: it produces one of two values, so it can be used inside a template literal, after return or as an argument. Both convert the condition with the truthiness rules, and both run only the branch they chose, so user ? user.name : 'guest' never reads user.name when user is null. Use the ternary for a short choice between two values, and an if statement when the branches do work, when there are several cases, or when ternaries would have to be nested inside each other.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to mid

    A refund function has five levels of nested if and else blocks, and a bug report says that customers who paid by gift card get the wrong message. How would you restructure the function, and how would you show that the new version gives the same answers as the old one in every other case?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Fresher to mid

    In a code review you find const fee = isMember ? 0 : total > 500 ? 0 : remote ? 99 : 49; and its tests pass. Would you approve it? Explain how JavaScript groups it, what each kind of customer pays, and what you would ask the author to change.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • An if runs its block only when the condition counts as true, and else runs the other block. Always write the braces: without them an if controls one statement, and an else belongs to the nearest if without one.
  • A condition is converted like Boolean(value): an empty array or object counts as true, 0 and '' as false. Test what you mean, such as items.length > 0.
  • In an else if chain only the first condition that holds runs its block. Put the narrowest band first, finish with else, and test each limit and the value just past it.
  • x = 0 stores a value rather than comparing two, and status === 'paid' || 'refunded' holds for every status; ESLint’s recommended rules catch both.
  • condition ? a : b chooses a value and evaluates only the side it chose; use if when the branches do things.
  • Guard clauses check for problems first and return or throw early, so the normal case stays flat; a Map can replace a chain that maps fixed values.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.