If/Else Statements — Making Decisions in Code

8 min read

Test code is mostly decisions. Did the request return 200? Did the response come back fast enough? Did the response body contain what we expected? Each of those is a yes/no question — and JavaScript's if/else statement is how you ask them. This lesson teaches the syntax, the patterns you'll use every day, and the structural pitfalls beginners hit.

The basic if

if runs a block of code only when a condition is true.

const statusCode = 200;
if (statusCode === 200) {
  console.log("Test passed");
}

Output:

Test passed

If the condition is false, the block is skipped. Try running with statusCode = 500 and the script prints nothing.

=== is strict equality (the next lesson covers it in detail — for now, just use it everywhere). The condition lives inside (). The body lives inside {}. Both are required.

Adding else

else runs when the if condition is false. It's how you write pass/fail logic.

const statusCode = 500;
if (statusCode === 200) {
  console.log("✅ Test passed");
} else {
  console.log("❌ Test failed");
}

Output:

❌ Test failed

Exactly one of the two branches runs. There's no scenario where both run, and no scenario where neither runs.

else if for multiple branches

When you have more than two outcomes, chain else if. Each condition is checked in order; the first one that's true runs, and the rest are skipped.

A real example: HTTP status codes fall into ranges that mean different things (200s = success, 300s = redirects, 400s = client error, 500s = server error). A small helper that classifies a response:

const statusCode = 404;
 
if (statusCode >= 200 && statusCode < 300) {
  console.log("success");
} else if (statusCode >= 300 && statusCode < 400) {
  console.log("redirect");
} else if (statusCode >= 400 && statusCode < 500) {
  console.log("client error");
} else if (statusCode >= 500 && statusCode < 600) {
  console.log("server error");
} else {
  console.log("unexpected status code");
}

Output:

client error

The order matters. JavaScript checks each condition top-to-bottom and stops at the first hit. The trailing else is your safety net for anything that didn't match above — useful for catching API responses with unexpected codes.

Always use curly braces

JavaScript lets you skip the {} if there's only one line in the body:

// Legal but dangerous
if (statusCode === 200) console.log("passed");

Don't. Add a second line later — say, increment a counter — and forget to add braces, and you've created a silent bug:

if (statusCode === 200)
  console.log("passed");
  testsPassed++;          // ← always runs, regardless of statusCode

The indentation makes the second line look like it's inside the if. It isn't. Always use braces, even for one-liners. ESLint can enforce this with the curly rule.

A real check — response time within SLA

Two things at once: the response time has to be acceptable AND we should know how close to the limit we got.

const responseTime = 1750; // milliseconds
const maxAllowed = 2000;
 
if (responseTime < maxAllowed) {
  console.log(`✅ Response time OK (${responseTime}ms)`);
} else {
  console.log(`❌ Response time exceeded SLA (${responseTime}ms / ${maxAllowed}ms)`);
}

Output:

✅ Response time OK (1750ms)

This is the shape of about 80% of test assertions you'll write — compute a value, compare against a threshold, branch on the result.

Decision flow for a full test

A real test has multiple checks layered together. Status code, then response time, then body shape — each step can fail the test.

Avoid deep nesting — use early returns

A naive translation of the diagram into nested ifs:

function check(response) {
  if (response.statusCode === 200) {
    if (response.time < 2000) {
      if (response.body.userId) {
        return "✅ pass";
      } else {
        return "❌ no userId";
      }
    } else {
      return "❌ slow";
    }
  } else {
    return "❌ bad status";
  }
}

That works, but the indentation marches off the right edge of the screen — and the important line (return "✅ pass") is buried four levels deep. Prefer an early-return style: deal with each failure case first, then write the happy path at the bottom.

function check(response) {
  if (response.statusCode !== 200) return "❌ bad status";
  if (response.time >= 2000)       return "❌ slow";
  if (!response.body.userId)       return "❌ no userId";
  return "✅ pass";
}
 
console.log(check({ statusCode: 200, time: 1500, body: { userId: 42 } }));

Output:

✅ pass

Same logic, half the nesting. Reading top-to-bottom you can see "all the ways this fails, then the success." That pattern scales — you can add new failure conditions without re-indenting any existing code.

⚠️ Common mistakes

  • Using = instead of == or ===. if (statusCode = 200) assigns 200 to statusCode and then evaluates as truthy — so the if always runs. Always use === for comparison. Most linters flag the assignment-in-condition pattern; turn that rule on.
  • Skipping braces, then adding a line. The silent bug from a forgotten {} is one of the most common beginner errors. Always brace, even if it's one line today.
  • Re-checking the same condition. Beginners sometimes write if (x === 200) { ... } else { if (x !== 200) { ... } } — the inner check is redundant because else already means "the if was false." Trust JavaScript to do this for you.

🎯 Practice task

Build a status code classifier. 15-20 minutes.

  1. In your js-for-qa folder, create classify.js.
  2. Declare const statusCode = 404; (you'll change this value as you test).
  3. Write an if/else if/else chain that prints:
    • "success" for 200-299
    • "redirect" for 300-399
    • "client error" for 400-499
    • "server error" for 500-599
    • "unexpected status code" for anything else
  4. Run with node classify.js and verify it prints "client error".
  5. Change statusCode to 200, 301, 503, and 999 and rerun each time. Confirm the output matches.
  6. Stretch: wrap the logic in a function named classify(code) that returns the string instead of logging it. Use early-return style — one return per branch, no else. Call it for several status codes and console.log each result.

The next lesson goes deep on the comparison operators (===, >=, etc.) and the logical operators (&&, ||, !) you'll use to build richer conditions.

// tip to track lessons you complete and pick up where you left off across devices.