Flow Control

A computer program is like a journey for your data. During this journey, data encounters situations that impact it, changing it forever. Like any journey, one must travel a given path. On that path, there are different roads the data can take. Sometimes the data takes one road; sometimes it takes another. Which roads the data takes depends on the program's end goal.

When writing programs, you want your data to take the correct path. You want it to turn left or right, up, down, reverse, or proceed straight ahead when it's supposed to. We call this flow control.

How do we make data do the right thing? We use conditionals.

Conditionals

A conditional is a fork (or multiple forks) in the road. Your data arrives at a conditional, which then tells the data where to go. The simplest conditionals use a combination of if statements with comparison and logical operators (<, >, <=, >=, ==, ===, !=, !==, &&, ||) to direct traffic. They use the keywords if and else.

That's enough talking; let's write some code. Create a file named conditional.js with the following content:

// Run this code in your browser with an HTML file

let a = prompt('Enter a number');

if (a === '3') {
  console.log("a is 3");
} else if (a === '4') {
  console.log("a is 4");
} else {
  console.log("a is neither 3, nor 4");
}

Here, we use the browser's prompt function to get a number from the user.

Run conditional.js at least three times. You can rerun the code in your browser by refreshing the window.

  • The first time, enter the value 3.
  • The second time, enter the value 4.
  • The third and subsequent times, input any other value.

This code uses the === operator to determine whether the input value is equal to '3' and, if not, whether it is equal to '4'. If the value is '3', the program runs line 6. If the value is '4', it runs line 8. If the value is something else, it runs line 10. As you can see, we can control the program's flow by setting conditionals in an if statement. Nice work!

Note that prompt returns a string value, so our comparisons compare a against string values.

The examples below are all valid JavaScript conditionals.

if (x === 3) {                          // Example 1
  console.log("x is 3");
}

if (x === 3) {                          // Example 2
  console.log("x is 3");
} else {
  console.log("x is NOT 3");
}

if (x === 3) console.log("x is 3");     // Example 3

if (x === 3)                            // Example 4
  console.log("x is 3");

if (x === 3)                            // Example 5
  console.log("x is 3");
else
  console.log("x is NOT 3");

if (x === 3) {                          // Example 6
  console.log('x is 3');
} else {
  if (x === 4) {
    console.log('x is 4');
  } else {
    console.log('x is NOT 3 or 4');
  }
}

if (x === 3) {                          // Example 7
  console.log("x is 3");
} else if (x === 4) {
  console.log("x is 4");
} else {
  console.log('x is NOT 3 or 4');
}

Example 1 demonstrates the simplest if statement: it has a single condition (x === 3) and a single clause—a block, statement, or expression in this context—that executes when the condition is true. When the condition is false, execution resumes with the first statement or expression after the if statement without running the code in the clause.

Example 2 demonstrates that you can handle both true and false conditions in the same if statement by using an else clause. When the condition is true, the code in the if clause (the first block) runs; when it's false, the code in the else clause runs. It's important to understand that the else clause is not a separate statement: it's part of the if statement.

Examples 3, 4, and 5 show that you don't need a block when the if or else clause contains a single statement or expression. You need braces for a block when you want to execute multiple statements or expressions in a clause. Otherwise, you can omit them. However, this practice can cause problems. Consider the following code:

if (x === 3)
  console.log('x is 3');
  console.log('x is an odd number');

Based on the indentation, it looks like the programmer expects line 3 to execute when x is 3, but not when it has some other value. However, line 3 is not part of the if statement. It's a separate expression that follows the if statement. Though JavaScript allows this practice, you should avoid it in most cases. Blocks make your code more readable and reliable.

Examples 6 and 7 both behave the same way. Example 6 uses a nested if statement in the else clause, while example 7 flattens out the body of the else block into an else if clause. It's easier to read and maintain example 7 since you don't have the syntactic clutter of extra braces and indentation.

Comparisons

Let's look at the comparison operators in some more depth so you can build more complicated conditional statements. One thing to remember is that comparison operators return a boolean value: true or false. We'll play with them in node to see how they work.

The expressions or values that an operator uses are its operands. In comparisons, the expressions to the left and right of the operator are the operands. For instance, the equality comparison x === y uses the === operator with two operands: x and y.

  • ===
    The strict equality operator, also known as the identity operator, returns true when the operands have the same type and value, false otherwise. We discussed === in The Basics chapter. It should be familiar even if it still looks strange.

    > 5 === 5
    = true
    
    > 5 === 4
    = false
    
    > 'abc' === 'abc'
    = true
    
    > 'abc' === 'abcd'
    = false
    
    > 'abc' === 'aBc'
    = false
    
    > '5' === '5'
    = true
    
    > '5' === '6'
    = false
    
    > 5 === '5'
    = false
    
    > '' === 0
    = false
    

    Notice that we can compare strings for strict equality. To be strictly equal, two strings must have the exact same value. The specific value doesn't matter, just that both strings have the same value. If there is any difference at all, the strings are not equal.

    We threw these last two examples in as a reminder that two values must have the same type or they are not equal. Thus, the string '5' is not the same as the number 5. The comparison is thus false. Furthermore, the last example shows that two different "falsy" values (described later in this chapter) are not equal.

  • !==
    The strict inequality operator returns false when the operands have the same type and value, true otherwise. Note that !== is the inverse of ===: when === returns true, !== returns false, and vice versa.

    // Compare with the `===` examples.
    
    > 5 !== 5
    = false
    
    > 5 !== 4
    = true
    
    > 4 !== 156
    = true
    
    > 'abc' !== 'def'
    = true
    
    > 'abc' !== 'aBc'
    = true
    
    > 5 !== '5'
    = true
    

    As with ===, we can easily compare two values of the same type for strict inequality and get reasonable results. However, if the two values have different types, the return value is true.

  • ==
    The non-strict equality operator, also known as the loose equality operator, is similar to ===. However, when the operands have different types, == attempts to coerce one of the operands to the other operand's type before it compares them, and it may coerce both operands in some cases. The result is true when the final values are the same, false otherwise. The coercion behavior can lead to unexpected results. For instance, when we compare the number 5 to the string '5' using ==, we get true; with ===, we get false.

    // Compare with the `===` examples.
    
    > 5 == 5
    = true
    
    > 5 == 4
    = false
    
    > 5 == '5'
    = true
    
    > '' == 0
    = true
    
  • !=
    The non-strict inequality operator, also known as the loose inequality operator, is similar to !==. However, when the operands have different types, != attempts to coerce one of the operands to the other operand's type before it compares them, and it may coerce both operands in some cases. The result is false when the final values are the same, true otherwise.

    // Compare with the `==` and `!==` examples.
    
    > 5 != 5
    = false
    
    > 5 != 4
    = true
    
    > 5 != '5'
    = false
    
    > '' != 0
    = false
    

    The rules that govern which operand == and != coerces to the other are complex and difficult to remember. Avoid these operators when you can. For instance, you can use explicit coercion and === in most cases.

    That advice is not universal. There are JavaScript developers, including some well-known ones, who will tell you to go ahead and use the loose operators, == and !=. Their reasoning is easy to understand: your code should not be attempting to compare different kinds of things, except in a few well-defined, isolated cases. Using the strict operators as a workaround is just masking bad code. They're not completely wrong! If you're comparing strings with arrays, your code almost certainly needs a redesign.

    That said, there are some edge cases that you need to be aware of with the loose operators. For that reason, the style we use at Launch School insists that you always use the strict operators. Doing so won't prevent you from having to fix bad code, but at this stage of your journey, it's less confusing to use the strict operators, and easier to debug.

  • <
    The less than operator returns true when the value of the left operand has a value that is less than the value of the right operand, false otherwise.

    > 4 < 5
    = true
    
    > 5 < 4
    = false
    
    > 5 < 5
    = false
    
    > "4" < "5"
    = true
    
    > "42" < "402"
    = false
    
    > "42" < "420"
    = true
    
    > "42" < 420
    = true
    

    The examples with strings are especially tricky! Make sure you understand them. When comparing strings, the comparison is character-by-character. JavaScript moves from left-to-right in the strings looking for the first character that is different from its counterpart in the other string. Once it finds a character that differs, it compares that character with its counterpart, and makes a decision based on that. If both strings are equal up to the length of the shorter string as in the next to last example, then the shorter string is considered less than the longer string.

    The final example shows that if you use < with two different types, some sort of coercion will take place. In this case, "42" gets coerced to a number, so a numeric comparison takes place. Don't try to remember this.

  • >
    The greater than operator returns true when the value of the left operand has a value that is greater than the value of the right operand, false otherwise.

    // Compare with the `<` examples.
    
    > 4 > 5
    = false
    
    > 5 > 4
    = true
    
    > 5 > 5
    = false
    
    > "4" > "5"
    = false
    
    > "42" > "402"
    = true
    
    > "42" > "420"
    = false
    
    > "42" > 420
    = false
    

    As with <, the > operator can be used to compare strings, and can even be used with mixed types (but with sometimes bizarre results).

  • <=
    The less than or equal to operator returns true when the value of the left operand has a value that is less than or equal to the value of the right operand, false otherwise. Note that =< is not a valid comparison operator.

    // Compare with the `<` examples.
    
    > 4 <= 5
    = true
    
    > 5 <= 4
    = false
    
    > 5 <= 5
    = true
    

    Of course, the <= operator works equally well with strings.

  • >=
    The greater than or equal to operator returns true when the value of the left operand has a value that is greater than or equal to the value of the right operand, false otherwise. Note that => is not a valid comparison operator.

    // Compare with the `>` examples.
    
    > 4 >= 5
    = false
    
    > 5 >= 4
    = true
    
    > 5 >= 5
    = true
    

    Of course, the >= operator works equally well with strings.

Logical Operators

You're beginning to get a decent grasp of basic conditional flow. Let's take a few minutes to see how we can combine multiple conditions to create more specific and complex scenarios. The !, &&, and || logical operators provide the ability to combine conditions:

  • !
    The not operator returns true when its operand is false and returns false when the operand is true. That is, it negates its operand. Note that, unlike most operators, ! takes a single operand; the operand appears to the right of the operator.

    > !true
    = false
    
    > !false
    = true
    
    > !(4 === 4)
    = false
    
    > !(4 !== 4)
    = true
    

    In these examples, JavaScript first evaluates the expression on the right, then applies ! to the result, thus negating it. For instance, we know that 4 === 4 is true, so !(4 === 4) is false.

    You may encounter code that uses !! in some expressions. We'll discuss that a little later in this chapter.

  • &&
    The and operator returns true when both operands are true and false when either operand is false.

    > true && true
    = true
    
    > true && false
    = false
    
    > false && true
    = false
    
    > false && false
    = false
    
    > (4 === 4) && (5 === 5)
    = true
    
    > (4 === 4) && (5 === 6)
    = false
    
    > (4 === 5) && (5 === 5)
    = false
    
    > (4 === 5) && (5 === 6)
    = false
    
  • ||
    The or operator returns true when either operand is true and false when both operands are false.

    > true || true
    = true
    
    > true || false
    = true
    
    > false || true
    = true
    
    > false || false
    = false
    
    > (4 === 4) || (5 === 5)
    = true
    
    > (4 === 4) || (5 === 6)
    = true
    
    > (4 === 5) || (5 === 5)
    = true
    
    > (4 === 5) || (5 === 6)
    = false
    

&& and || don't always return true or false, but they do when they operate on boolean values. A little later in this chapter we'll see what happens when we use && and || with non-boolean values.

Short Circuits

The && and || operators both use a mechanism called short circuit evaluation to evaluate their operands. Consider these two expressions:

> isRed(item) && isPortable(item)
> isGreen(item) || hasWheels(item)

The first expression returns true when item is both red and portable. If either condition is false, then the overall result must be false. Thus, if the program determines that item is not red, it doesn't have to check whether it is portable. JavaScript short-circuits the entire expression by terminating evaluation as soon as it determines that item isn't red. It doesn't need to call isPortable() since it already knows that the entire expression must be false.

Similarly, the second expression returns true when item is either green or has wheels. When either condition is true, the overall result must be true. Thus, if the program determines that item is green, it doesn't have to check whether it has wheels. Again, JavaScript short-circuits the entire expression once it determines that item is green. The entire expression must be true.

Truthiness

Notice that every if statement has an expression that evaluates as true or false. However, the expression doesn't have to be one of the boolean values, true and false. JavaScript can coerce any value to a boolean value, and that's what it does in conditional contexts like the if statement.

For instance, you can write code like this:

let a = 5
if (a) {
  console.log("how can this be true?");
} else {
  console.log("it is not true");
}
let b = 0
if (b) {
  console.log("how can this be true?");
} else {
  console.log("it is not true");
}

The first example logs "how can this be true?" while the second logs "it is not true." This works since JavaScript coerces the value 5 to true, and the value 0 to false. To repeat, JavaScript can coerce any value to a boolean. Thus, you can use any expression in a conditional expression. We often say that the expression evaluates as or evaluates to true or false.

We can even write code like this:

let x;

if (x = 5) {
  console.log("how can this be true?");
} else {
  console.log("it is not true");
}

The above code doesn't test whether x is equal to 5. Instead, it assigns the variable x to 5, then evaluates the assignment's return value (5) as a boolean. Here, 5 evaluates as true when it appears in a boolean expression.

Why is that? The answer is simple: when coercing a value to a boolean, JavaScript treats the following values as false:

  • false
  • The number 0. This includes all 3 variations of zero in JavaScript:
    • 0: The ordinary zero value.
    • -0: A negative zero. That's mathematical nonsense, but a real thing in JavaScript.
    • 0n: The BigInt version of zero.
  • An empty string ('')
  • undefined
  • null
  • NaN

Everything else evaluates as true.

We often use the term falsy to refer to values that evaluate as false, while the values that evaluate as true are truthy. We use these terms when we need to distinguish between boolean true and false values. We can also discuss truthiness: whether something is a truthy or falsy value.

Truthiness is exceptionally useful in JavaScript; there are plenty of situations where you want to treat the values 0 (all 3 variants), '', undefined, null, and NaN as though they were false. It helps make conditional expressions read more naturally, but it can also catch an unwary programmer by surprise. If you have experience with another language that uses falsy values, be wary; most languages don't share the same idea of what values are falsy. That's a constant headache for programmers that work with multiple languages.

Let's return to the if (x = 5) example. When you see code like that, it's important to remember that x = 5 is an assignment. It returns 5, which, in turn, is a truthy value. You should avoid using assignments in conditionals: at first glance, if (x = 5) and if (x == 5) look identical. However, they have entirely different meanings and produce different results. That makes the code suspect: the assignment might be intentional, but it might also be a mistake, and mistakes are bugs waiting to bite the unwary. Worse yet, another programmer may come along and naively "fix" the code.

The && and || logical operators, as you'll recall, use short-circuit evaluation. These operators work with truthy and falsy values too, and they can also return truthy values instead of boolean values. When using && and ||, the return value is always the value of the operand evaluated last:

> 3 && 'foo'  // last evaluated operand is 'foo'
= 'foo'

> 'foo' && 3  // last evaluated operand is 3
= 3

> 0 && 'foo'  // last evaluated operand is 0
= 0

> 'foo' && 0  // last evaluated operand is 0
= 0
> 3 || 'foo'  // last evaluated operand is 3
= 3

> 'foo' || 3  // last evaluated operand is 'foo'
= 'foo'

> 0 || 'foo'  // last evaluated operand is 'foo'
= 'foo'

> 'foo' || 0  // last evaluated operand is 'foo'
= 'foo'

> '' || 0     // last evaluated operand is 0
= 0

Suppose you have an expression of some kind that returns a value that is either truthy or falsy, but isn't a boolean value:

let foo = null;
let bar = 'qux';
let isOk = foo || bar;

In this code, isOk gets set to a truthy value of "qux". In most cases, you can use "qux" as though it were actually a boolean true value. However, using a string value as though it is a boolean isn't the clearest way to write your code. It may even look like a mistake to another programmer who is trying to track down a bug. In some strange cases, it may even be a mistake.

You can address this easily enough by using an if statement or a ternary expression (introduced in a few sections):

// if statement
let isOk;
if (foo || bar) {
  isOk = true;
} else {
  isOk = false;
}
// ternary expression
let isOk = (foo || bar) ? true : false;

Either of those snippets sets isOk to an appropriate boolean value. However, they do so in a somewhat wordy manner. Many JavaScript programmers use a more concise coercion by using what looks like a !! operator:

let isOk = !!(foo || bar);

In reality, !! isn't a separate operator in JavaScript. Instead, it's two consecutive ! operators. The expression !!a is equivalent to writing !(!a). The inner ! converts the value of a to false if it is truthy, or true if a is falsy. The outer ! then flips true to false or false to true. In the end, we end up with a boolean value instead of a truthiness value:

> !!3    // 3 is truthy, !3 is false, !false is true
= true

> !!''   // '' is falsy, !'' is true, !true is false
= false

Nullish Coalescing Operator

The nullish coalescing operator evaluates to the right-hand operand if the left-hand operand is nullish (either null or undefined). Otherwise, it evaluates to the value of the left-hand operand.

> null ?? "over here!"
= 'over here!'

> undefined ?? "pick me!"
= 'pick me!'

> false ?? "not me"
= false

> 0 ?? "not me either"
= 0

This is very similar to the || operator, but there's an important difference:

  • || will return the right-hand operand if the left operand is a falsy value.
  • ?? will return the right-hand operand if the left operand is nullish.

The nullish coalescing operator will short-circuit if the left operand is not nullish:

> null ?? "over here!"    // does not short-circuit
= 'over here!

> undefined ?? "pick me!" // does not short-circuit
= 'pick me!'

> false ?? "not me"       // short-circuits
= false

> 0 ?? "not me either"    // short-circuits
= 0

?? is most useful when dealing with code that could result in undefined or null, and you want to handle the situation gracefully:

function foo(str) {
  let found = ["Pete", "Alli", "Chris"].find(name => name === str);
  return found ?? "Not found";
}

console.log(foo("Alli"));     // => Alli
console.log(foo("Allison"));  // => Not found

The find method is an instance method for arrays. We'll cover find in-depth during the Core Curriculum, but for now, just be aware that line 2 will return either one of the elements in the array or undefined.

We don't use the nullish coalescing operator much in the Core curriculum. It will be far more useful if you attend Capstone.

Operator Precedence

JavaScript has a set of precedence rules it uses to evaluate expressions that use multiple operators and sub-expressions. The following is a list of the comparison operations from the highest precedence (top) to lowest (bottom).

  • <=, <, >, >= - Comparison
  • ===, !==, ==, != - Equality
  • && - Logical AND
  • || - Logical OR

With the precedence list in hand, we can look at the following expression and determine how to evaluate it:

if (x || y && z) {
  // do something
}

For the moment, let's ignore the fact that both || and && are short-circuit operators. The program first evaluates the y && z sub-expression since && has higher precedence than ||. It then takes the result of that evaluation and evaluates x || result.

We can use parentheses to override the precedence: sub-expressions in parentheses get evaluated before un-parenthesized expressions at the same depth in the main expression (don't worry about what we mean by depth right now):

if ((x || y) && z) {
  // do something
}

In this code, x || y gets evaluated first, and then result && z. That's a different result from the un-parenthesized expression. Parentheses help the computer and other programmers understand your intentions; you should strive to use parentheses in any expression that uses two or more different operators.

JavaScript evaluates parentheses in the usual algebraic order. That is, it evaluates the expression in the innermost set of parentheses first, then works its way out to the outermost part of the expression. When multiple parenthesized subexpressions appear at the same depth, it evaluates them from left to right. Once it evaluates the parenthesized expressions, it evaluates the final expression value.

Short circuit evaluation doesn't change the precedence rules, but, if you try to think about it, you may end up confused. Wait until later before you try to understand how and why. For now, remember that short-circuit evaluation may prevent JavaScript from evaluating the expression to the right of the operator, but the precedence rules remain the same.

The Ternary Operator

The ternary operator is a quick and easy way to write a short, concise, and simple if/else conditional. It uses a combination of the ? and : symbols and takes 3 operands (hence, the name "ternary"):

> 1 == 1 ? 'this is true' : 'this is not true'
= 'this is true'

> 1 == 0 ? "this is true" : "this is not true"
= 'this is not true'
 

How does this work? JavaScript first evaluates the first operand (the comparisons). If it has a truthy result, JavaScript evaluates the second operand (this is true) and returns its value. Otherwise, it evaluates the third operand (this is not true) and returns its value.

The chief advantage that the ternary operator has over an if/else statement is that the entire structure is an expression. What that means is that we can treat the ternary expression as a value: we can assign it to a variable, pass it as an argument, and so on. Since if/else is a statement, we can't capture its result to a variable.

> let message = true ? 'this is true' : 'this is not true'
= undefined

> message
= 'this is true'

> console.log(false ? 'this is true' : 'this is not true')
this is not true
= undefined

You can't do that with an if/else statement.

If you feel unsure of how this works, play around with it in node and test some other cases. Nothing creates familiarity faster than repeated exposure and experimentation.

When Should I Use a Ternary Expression?

Ternary expressions should usually be used to select between 2 values, not to choose between two actions. (An action would be something like logging a value to the console or setting a variable to a new value.) The ternary expression's result should almost always be assigned to a variable, passed to a function as an argument, or returned by a function. If you're not doing one of those things, an if/else statement is a better choice.

For example, all of the following are good examples of using a ternary expression:

let foo = hitchhiker ? 42 : 3.1415;    // Assign result of ?: to a variable
console.log(hitchhiker ? 42 : 3.1415); // Pass result as argument
return hitchhiker ? 42: 3.1415;        // Return result

However, the following snippets use ternaries that choose between actions, and should be considered inappropriate uses:

hitchhiker ? foo = 42 : bar = 3.1415;               // Setting variables
hitchhiker ? console.log(42) : console.log(3.1415); // Printing

In general, all components of a ternary expression should be relatively simple expressions. Aim for readability, not brevity.

Switch Statement

The last conditional flow structure we want to discuss is the switch statement. A switch statement is similar to an if statement, but it has a different interface. It compares a single value against multiple values for strict equality (as with the === operator), whereas if can test multiple expressions with any condition.

switch statements use the reserved words switch, case, default, and break. It's often easier to show rather than tell, and that's certainly the case with the switch statement. First, create a file named switch.js with this content:

let a = 5;

switch (a) {
  case 5:
    console.log('a is 5');
    break;
  case 6:
    console.log('a is 6');
    break;
  default:
    console.log('a is neither 5, nor 6');
    break;
} // => a is 5

This example is functionally identical to the following if/else statement:

let a = 5;

if (a === 5) {
  console.log('a is 5');
} else if (a === 6) {
  console.log('a is 6');
} else {
  console.log('a is neither 5, nor 6');
} // => a is 5

You can see how similar they are, but you can also see how they differ. The switch statement evaluates the expression, a, compares its value to the value in each case clause and then executes the statements and expressions associated with the first matching clause. In this example, the value of the expression is 5; thus, the program executes the statements and expressions associated with the case 5: clause. The statements and expressions in the default: clause run when the expression doesn't match any of the case clauses; it acts like the final else in an if statement.

The break statement in each case is crucial. Without a break, execution "falls through" to the next case clause.

let a = 5;

switch (a) {
  case 5:
    console.log('a is 5');
  case 6:
    console.log('a is 6');
  default:
    console.log('a is neither 5, nor 6');
} // => a is 5
  //    a is 6
  //    a is neither 5, nor 6

This behavior is a little strange, and almost always undesirable. In most cases, you want to avoid fall-throughs that lead to executing multiple case clauses for a single value. Code that falls through multiple cases like this is, by its nature, suspect; it looks like you forgot to use the break statement, which makes it look like a bug waiting to happen. Even if the code is correct, it looks wrong. Often, it is wrong.

However, that doesn't mean that fall-throughs are never appropriate. There are use cases where they work well. For instance, suppose you want to execute the same action for two or more cases:

let a = 5;

switch (a) {
  case 5:
  case 6:
  case 7:
    // executed if a is 5, 6, or 7
    console.log("a is either 5, 6, or 7");
    break;
  case 8:
  case 9:
    // executed if a is 8 or 9
    console.log('a is 8 or 9');
    break;
  default:
    // executed if a is anything else
    console.log('a is not 5, 6, 7, 8, or 9');
    break;
}

Technically, this is fall-through, but, since each case executes a single clause, it's safe to use and doesn't suggest a possible error.

There are plenty of uses for switch statements. They're potent tools in JavaScript. If you're uncomfortable with them, spend some time modifying the ones we presented above and watch how they respond to your changes. Test their boundaries and learn their capabilities. Curiosity will serve you well in your journey towards mastering JavaScript. There is much to discover!

Summary

This chapter covered booleans, comparisons, and using conditionals to control the flow of code execution. These are some of the fundamental tools you'll need to become a JavaScript developer.

Exercises

  1. What values do the following expressions evaluate to?

    false || (true && false);
    true || (1 + 2);
    (1 + 2) || true;
    true && (1 + 2);
    false && (1 + 2);
    (1 + 2) && true;
    (32 * 4) >= 129;
    false !== !true;
    true === 4;
    false === (847 === '847');
    false === (847 == '847');
    (!true || (!(100 / 5) === 20) || ((328 / 4) === 82)) || false;
    

    Solution

    Expression Value
    false || (true && false) false
    true || (1 + 2) true
    (1 + 2) || true 3
    true && (1 + 2) 3
    false && (1 + 2) false
    (1 + 2) && true true
    (32 * 4) >= 129 false
    false !== !true false
    true === 4 false
    false === (847 === '847') true
    false === (847 == '847') false
    (!true || (!(100 / 5) === 20) || ((328 / 4) === 82)) || false true

    Remember that expressions involving the logical operators of || and && use short-circuit evaluation; they also return truthiness values.

    The last expression needs some explanation:

    1. !true is false
    2. !(100 / 5) === 20) is also false because !(100 / 5) is a boolean value, and === always returns false when the operands have different types.
    3. ((328 / 4) === 82) is true
    4. Put together, items 1-3 combine with || as (false || false || true), which is true.
    5. Using the value from item 4 results in true || false, which is true.

    Video Walkthrough

    Please register to play this video

  2. Write a function, evenOrOdd, that determines whether its argument is an even number. If it is, the function should log 'even' to the console; otherwise, it should log 'odd'. For now, assume that the argument is always an integer.

    A number is even if you can divide it by two with no remainder. For instance, 4 is even since 4 divided by 2 has no remainder. Conversely, 3 is odd since 3 divided by 2 has a remainder of 1.

    You can use the % remainder operator shown in The Basics chapter to determine the remainder.

    Solution

    function evenOrOdd(number) {
      if (number % 2 === 0) {
        console.log('even');
      } else {
        console.log('odd');
      }
    }
    

    The solution uses the remainder operator (%) to determine whether the number is even. If the result of number % 2 is 0, the number is even.

    Video Walkthrough

    Please register to play this video

  3. Let's improve our previous implementation of evenOrOdd. Add a validation check to ensure that the argument is an integer. If it isn't, the function should issue an error message and return.

    You can use Number.isInteger(value) to determine whether a numeric value is an integer. It returns true if the value is an integer, false otherwise.

    Solution

    function evenOrOdd(number) {
      if (!Number.isInteger(number)) {
        console.log('Sorry, the value you passed is not an integer');
        return;
      }
    
      if (number % 2 === 0) {
        console.log('even');
      } else {
        console.log('odd');
      }
    }
    

    Video Walkthrough

    Please register to play this video

  4. What does the following code log to the console, and why?

    function barCodeScanner(serial) {
      switch (serial) {
        case '123':
          console.log('Product1');
        case '113':
          console.log('Product2');
        case '142':
          console.log('Product3');
        default:
          console.log('Product not found!');
      }
    }
    
    barCodeScanner('113');
    

    Solution

    The output is:

    Product2
    Product3
    Product not found!
    

    Since the case clauses of the switch statement lack break statements, control falls through from the matching case '113' clause and executes the code in the case '142' and default clauses as well. JavaScript doesn't care that the criteria for extra case clauses don't match our serial value. This fall-through behavior is often undesirable. To avoid it, you must add break statements to each case clause:

    function barCodeScanner(serial) {
      switch (serial) {
        case '123':
          console.log('Product1');
          break;
        case '113':
          console.log('Product2');
          break;
        case '142':
          console.log('Product3');
          break;
        default:
          console.log('Product not found!');
      }
    }
    

    Video Walkthrough

    Please register to play this video

  5. Refactor this statement to use an if statement instead.

    return foo() ? 'bar' : qux();
    

    Solution

    if (foo()) {
      return 'bar';
    } else {
      return qux();
    }
    

    Ternary operators are most useful when the values are simple expressions; anything more complicated than calling a function or accessing a variable or literal value can lead to unreadable code. Our original code is an excellent example of how to use the ternary operator; the refactoring merely demonstrates that you understand how it works.

    Video Walkthrough

    Please register to play this video

  6. What does this code output to the console?

    function isArrayEmpty(arr) {
      if (arr) {
        console.log('Not Empty');
      } else {
        console.log('Empty');
      }
    }
    
    isArrayEmpty([]);
    

    Solution

    The output is Not Empty since, while the array is empty -- it has no elements and the length property is 0 -- it isn't falsy. Thus, JavaScript executes the first branch of the if statement.

    Video Walkthrough

    Please register to play this video

  7. Write a function that takes a string as an argument and returns an all-caps version of the string when the string is longer than 10 characters. Otherwise, it should return the original string. Example: change 'hello world' to 'HELLO WORLD', but don't change 'goodbye'.

    Solution

    function capsLong(string) {
      if (string.length > 10) {
        return string.toUpperCase();
      } else {
        return string;
      }
    }
    
    console.log(capsLong("Sue Smith"));     // => Sue Smith
    console.log(capsLong("Sue Robertson")); // => SUE ROBERTSON
    console.log(capsLong("Joe Thomas"));    // => Joe Thomas
    console.log(capsLong("Joe Stevens"));   // => JOE STEVENS
    
    function capsLong(string) {
      return ((string.length > 10) ? string.toUpperCase() : string);
    }
    
    console.log(capsLong("Sue Smith"));     // => Sue Smith
    console.log(capsLong("Sue Robertson")); // => SUE ROBERTSON
    console.log(capsLong("Joe Thomas"));    // => Joe Thomas
    console.log(capsLong("Joe Stevens"));   // => JOE STEVENS
    

    Video Walkthrough

    Please register to play this video

  8. Write a function that logs whether an integer is between 0 and 50 (inclusive), between 51 and 100 (inclusive), greater than 100, or less than 0.

    numberRange(25);
    numberRange(75);
    numberRange(125);
    numberRange(-25);
    

    Expected Output

    25 is between 0 and 50
    75 is between 51 and 100
    125 is greater than 100
    -25 is less than 0
    

    Solution

    function numberRange(number) {
      if (number < 0) {
        console.log(`${number} is less than 0`);
      } else if (number <= 50) {
        console.log(`${number} is between 0 and 50`);
      } else if (number <= 100) {
        console.log(`${number} is between 51 and 100`);
      } else {
        console.log(`${number} is greater than 100`);
      }
    }
    

    Video Walkthrough

    Please register to play this video

  9. Without running this code, what will it print?

    console.log(false ?? null);
    console.log(true ?? (1 + 2));
    console.log((1 + 2) ?? true);
    console.log(null ?? false);
    console.log(undefined ?? (1 + 2));
    console.log((1 + 2) ?? null);
    console.log(null ?? undefined);
    console.log(undefined ?? null);
    

    Solution

    console.log(false ?? null);         // => false
    console.log(true ?? (1 + 2));       // => true
    console.log((1 + 2) ?? true);       // => 3
    console.log(null ?? false);         // => false
    console.log(undefined ?? (1 + 2));  // => 3
    console.log((1 + 2) ?? null);       // => 3
    console.log(null ?? undefined);     // => undefined
    console.log(undefined ?? null);     // => null
    

    Remember that ?? returns the second operand if the first operand is either null or undefined. In all other situations, it returns the first operand.

  10. Without running this code, what will it print?

    function show(foo = undefined, bar = null) {
      console.log(`foo is ${foo ?? 3}, bar is ${bar ?? 42}`);
    }
    
    show(5, 7);
    show(0, 0);
    show(4);
    show();
    

    Solution

    show(5, 7);  // => foo is 5, bar is 7
    show(0, 0);  // => foo is 0, bar is 0
    show(4);     // => foo is 4, bar is 42
    show();      // => foo is 3, bar is 42
    

    In this problem, a default value is used as the parameter value if the corresponding argument is omitted. Here, the first parameter defaults to undefined, while the second defaults to null. However, our console.log statement has foo default to 3 if it is undefined or null, and bar defaults to 42 if it is undefined or null.