Skip to the content

If statements and conditional logic

Last updated: By Lukas BrandtLesson 6 of 115 min read

A script that does the same thing every time is only half useful. Conditions let it react: show a warning when a field is empty, mark an answer right or wrong, change a label once a number crosses a limit. The main tool for this in JavaScript is the if statement.

The shape of an if statement

const count = 172;

if (count > 165) {
  console.log('Too long');
} else if (count >= 120) {
  console.log('Good length');
} else {
  console.log('Too short');
}

The browser evaluates the condition in parentheses. If it is true, the block in braces runs and the rest of the chain is skipped. else if adds another test that is only tried when every earlier test failed, and else catches whatever is left. You can chain as many else if branches as you like, but only one block in the whole chain ever runs.

That is why order matters. Put the count >= 120 test first and a 172-character text matches it and gets called a good length, because the browser stops at the first true condition. Test the most extreme or most specific case first.

Comparison operators

Operator Meaning Example Result
=== equal, and the same type 5 === 5 true
!== not equal, or a different type 5 !== '5' true
== equal after converting types 5 == '5' true
!= not equal after converting types 0 != '' false
> greater than 10 > 9 true
< less than '10' < '9' true
>= greater than or equal to 120 >= 120 true
<= less than or equal to 166 <= 165 false
Parts links can earn us a commission. As an Amazon Associate I earn from qualifying purchases.

A few rows deserve a second look. Use === and !== by default. The double-equals versions convert both sides to a common type before comparing, which produces results such as 0 == '' and null == undefined both being true. Triple equals compares type and value with no conversion, so '5' === 5 is simply false, and you never have to memorize the conversion rules.

The '10' < '9' row is not a typo. When both sides are strings, JavaScript compares them character by character, the way a dictionary orders words, and '1' comes before '9'. Form fields always give you strings, so convert them with Number() before comparing sizes, as the functions lesson did for its calculator.

Combining conditions

  • && (and) is true only when both sides are true: count >= 120 && count <= 165.
  • || (or) is true when at least one side is true: event.key === 'Enter' || event.key === 'Escape'.
  • ! (not) flips a value: !field.value is true when the field is empty.

Parentheses make mixed conditions readable. (isAdmin && isLoggedIn) || isPreview says exactly what it means, while the same line without them makes the reader stop and work out which operator binds first (&& does).

Truthy and falsy

A condition does not have to be a real true or false. JavaScript converts whatever you give it, and only a short list of values counts as false: false, 0, '' (an empty string), null, undefined and NaN, plus -0 and 0n, which you will rarely meet. Those are the falsy values. Everything else is truthy, including the string '0', the string 'false', an empty array and any element.

That allows compact checks:

const warning = document.querySelector('.warning');

if (warning) {
  warning.classList.add('is-visible');
}

querySelector returns either an element, which is truthy, or null, which is falsy. The block runs only when the element exists, so a page without a .warning no longer throws an error.

Worked example: a summary length checker

Say you are writing short summaries for a page and want each one to land between 120 and 165 characters. This checker labels the text as you type and turns the message red when it runs long:

<label for="summary">Summary</label>
<textarea id="summary" rows="4" cols="50"></textarea>
<p id="length-note">Start typing.</p>

<style>
  .is-over { color: #b03a2e; font-weight: bold; }
</style>
const summary = document.querySelector('#summary');
const note = document.querySelector('#length-note');

function describeLength(count) {
  if (count === 0) {
    return 'Start typing.';
  } else if (count < 120) {
    return `${count} characters, ${120 - count} short of the minimum.`;
  } else if (count <= 165) {
    return `${count} characters, inside the range.`;
  } else {
    return `${count} characters, ${count - 165} over the limit.`;
  }
}

summary.addEventListener('input', () => {
  const count = summary.value.trim().length;
  note.textContent = describeLength(count);
  note.classList.toggle('is-over', count > 165);
});

trim() removes spaces and line breaks from both ends, so a stray space after the last word does not count. A string's length is its number of characters.

Since every branch in describeLength returns, the else keywords are strictly optional: a return ends the function, so later tests only run when earlier ones failed anyway. Many developers drop them in this situation. Keeping them makes the chain easier to read for now.

The last line uses a feature of classList.toggle from the CSS lesson that saves a whole if statement. With a second argument, toggle stops flipping and follows the condition instead: true adds the class, false removes it.

The ternary operator

For a choice between two values, there is a compact form, condition ? valueIfTrue : valueIfFalse:

const hidden = document.body.classList.contains('notes-hidden');
button.textContent = hidden ? 'Show notes' : 'Hide notes';

Keep it for short, simple choices like this one. Nest two ternaries inside each other and nobody can read the line, including you a week later.

Try it

Paste the checker into the online HTML editor and type until the message turns red. Then move the count <= 165 branch above the count < 120 branch and type a short sentence. The "short of the minimum" message never appears any more, because every count from 1 to 165 now stops at the first test that matches.