Control Flow

Sometimes there's code that you only want to execute under certain circumstances.

Some scenarios for this to happen are:

  • In K'tah! a zombie that walks over a patch of rough dirt from the architect should be slowed, but players should remain unaffected.

  • In K'tah! trying to register an account name with only 2 letters will yield an error.

  • In K'tah! if a zombie is close enough to a player, that player will take damage.


OK, so maybe I was obsessing over K'tah! examples, but who wouldn't?

In any event, to be more specific about the above, we can think about executing code only when certain conditions are met:

  • IF a zombie is standing on rough dirt THEN slow their movement.

  • IF your account name has only 2 letters THEN display an error message.

  • IF zombie is a small distance from a player THEN player's health--


Programming mechanisms that can change if or how often certain statements are executed are referred to under the heading of control flow.

A script's control flow dictates which statements in a script are executed, in what order, or with what repetition.

Let's look at our first candidate in control flow now!


If-Statements


An if-statement allows us to control when certain code is executed based on whether some condition is met. Optionally, we can also specify an else-clause that executes when that condition is *not* met.

If-statements are a vital tool in the design of a program's control flow.

Here is the anatomy of an if-statement with an else-clause (NB: the else-clause is optional):

  // If-statement
  if (condition) {
    // if-statement body
    // Executed if condition is truthy
  } 
  
  // If-else statement
  if (condition) {
    // if-statement body
    // Executed if condition is truthy
  } else {
    // else-clause body
    // Executed if condition is falsy
  }

In the if-else syntax above, the if-statement body will execute whenever the condition is truthy, and the else-clause body will execute whenever the condition is falsy.

In the if-statement syntax above, the condition can be any expression; if that expression evaluates to a truthy value, then the if-statement body will execute.

Note: an else-clause always follows an if-statement; i.e., you can never have an else clause unless you first have an if statement.

How about some examples?

Design a script that, in a full, explanatory sentence, alerts whether a prompted number is even or odd.

  let num = prompt("Enter a number!");
  
  // Condition: num is even
  if (num % 2 === 0) {
    alert("Even I can see that's even ;)");
  
  // Else-clause: num is odd
  } else {
    alert("Hmm... how odd... literally!");
  }

Design a script that prompts a user for their name and then alerts a custom message if they have the same first and last name.

  let name = prompt("Enter your first and last name!"),
      nameArr = name.split(" ");
  
  if (nameArr[0] === nameArr[1]) {
    alert("You have the same first and last name? Wicked.");
  } else {
    alert("Oh... OK... thanks for that");
  }

It is possible to chain if-else statements together in what is known as an if-ladder, the syntax of which is as follows:

  if (condition1) {
    // executes if condition1 truthy
  } else if (condition2) {
    // executes if condition1 falsy
    // AND condition 2 truthy
  } else {
    // executes if condition1 falsy
    // AND condition2 falsy
  }

Design a script that prompts a user for a numerical representation of a month of the year (e.g., 1 for January, 2 for "February", etc.) and alerts how many days that month has. Consider only non-leap years such that February (2) has 28 days, and every other month has 31 - (month - 1) % 7 % 2 days.



Iteration

They say that insanity is doing the same thing over and over and expecting different results...

...so clearly, when we want to do something over and over, we should be able to expect the outcome at each step or else we'll go insane.

Another key aspect of controlling the flow of our programs is the ability to repeat certain statements and instructions according to some format.

Consider, for example, our rendering of a bunch of clouds in our Grassy Knoll diorama:

See the Pen Sun with MORE Disco Clouds by Andrew Forney (@forns) on CodePen.

What's wrong with the style of what we've written above?

Defining all of those clouds individually and then calling drawCloud for each is very repetitive! Our code is again not DRY. Consider also what we would have to do to draw 100 clouds using the syntax we've chosen above -- cumbersome!


As if the above isn't motivation enough, why else might we want to repeat some behavior programmatically?

  • In K'tah! creating the horde of zombies is not done by the programmer one by one, laboriously, but rather, instructs the interpreter what we want to happen iteratively.

  • Suppose we want to prompt a user continuously until some condition is met (not recommended unless you want to annoy your page's visitors).

  • We might also want to verify that all elements of an array conform to some criteria.


Luckily, we have another tool that can help us out.

Iteration is a control flow mechanic to execute some statements repeatedly until some condition is met.

Let's look at some ways to do iteration in JavaScript...


The For-Loop


The for-loop is a statement for iteratively repeating code by defining four components:

  1. Iterator: a loop-local variable called the iterator, which is typically used to advance repetition towards the stopping condition.

  2. Condition: an expression evaluated to determine if the loop should iterate again.

  3. Post-loop Action: a statement that executes at the end of each loop, generally used to advance the iterator towards the stopping condition.

  4. Loop body: statements that are executed at each iteration as long as the condition is truthy.

  // Anatomy of a for-loop
  for (let iterator = value; condition; post_loop_action) {
    // Loop body: what gets repeated at each iteration
  }

It's a funky syntax to see, but it's pretty simple, let's look at the steps carefully:

  1. Declare / initialize the counter ONCE at the start of the loop. This is the first statement in the for-loop parentheses.

  2. Evaluate the condition.

    • If the condition's value is falsy, stop the loop here, skip the loop body, and continue with the script.

    • If the condition's value is truthy, continue to the next step.

  3. Execute statements in the loop body.

  4. Once the end of the loop body is reached, execute the post_loop_statement.

  5. After executing the post_loop_statement, return to step 2.


Let's look at a couple of examples:

  // Alerts the integers 1 to 5
  for (let i = 1; i <= 5; i++) {
    alert(i);
  }
  // Alerts all even numbers of arr
  let arr = [1, 3, 4, 6, 5, 8, 9];
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] % 2 === 0) {
      alert(arr[i]);
    }
  }

The for-loop is the most popular loop format, but there are many other ways to accomplish iterative tasks, which we will investigate later.

Write a script that (1) prompts a user for a sentence, (2) prompts a user for a word, and (3) alerts how many times the word from (2) appears in the sentence in (1).



Other Loop Formats

You should also know that there are some fancy loop shorthands for iterating over iterable structures in JavaScript.

These include objects and arrays (which, of course, are treated just like objects!), as well as some other structures we may examine later.

The for-in loop can be used to iterate over enumerable properties. Here, the defined iterator iterates over each key.

The for-in behaves just like a for-loop, except the stopping condition and post-loop action are defined for you: keep iterating onto the next key as long as there are keys you haven't iterated to.

The syntax for a for-in loop looks like:

  let pantry = {
    "bananas": 1,
    "cereal": 2,
    "peanut butter": 1
  };
  
  // item will be the *keys* of pantry:
  // "bananas", "cereal", and then "peanut butter"
  // in some order
  for (let item in pantry) {
    alert(item + ": " + pantry[item]);
  }

Note: we say that the iterator (let item above) will attain each of the object's (pantry above) in *some* order, though not necessarily the one listed. For this reason, you should not use a for-in loop when you are worried about order (like with items in an array sequence).


The for-of loop can be used to iterate over array values wherein we do not care about the associated key / index. Here, the defined iterator iterates over each value.

The syntax for a for-of loop looks like:

  let todo = [
    "make lecture",
    "clean house",
    "fall asleep on couch",
    "remake todo list"
  ];
  
  // item will be the *vales* of the todo list:
  // each of the above strings in sequence
  for (let item of todo) {
    alert(item);
  }

This approach is handy when we simply want to access the individual elements of a collection like an array, but don't necessarily need to refer to the index of each item.


The while-loop continually executes statements in its body as long as its condition is truthy.

Essentially, while loops are a simplified for-loop, and are generally used when an iterator is not required.

The syntax for a while-loop is as follows:

  while (condition) {
    // loop-body
  }
  // Politeness training script:
  while (prompt("What do we say?") !== "please") {
    alert("Mind your manners!");
  }

Armed with our new friend iteration, fix the stylistic issues we had with our multi-cloud rendering above! (solution below)

See the Pen Sun with MORE Disco Clouds (Fixed) by Andrew Forney (@forns) on CodePen.



Nested Loops

Recall that with loop-bodies, we can execute whatever statements internal to them as we please.

Could that mean... is it possible to... do you mean we could possibly...

Why yes! Of course, we can put loops inside of our loops... so we can loop while we loop dawg!

A nested loop is a programming tactic when a loop is placed inside of another loop.


Nested loops serve a variety of purposes; commonly, they are used for:

  • Iterating through elements of a multi-dimensional array

  • Comparing items to each other in a list or collection

  • ...and more!


  let treasureMap = [
    [".", ".", "."],
    ["T", ".", "."],
    [".", ".", "X"]
  ];
  
  // Indicates the position of the
  // treasure!
  for (let row = 0; row < treasureMap.length; row++) {
    let currentRow = treasureMap[row];
    for (let col = 0; col < currentRow.length; col++) {
      if (currentRow[col] === "X") {
        alert("Row " + row + " Col " + col);
      }
    }
  }



Dangers of Iteration

As you might imagine, there are some things that can go horribly horribly wrong with iteration...

In particular, we need to discuss everyone's favorite iteration-logic-error:

An infinite loop is a logic-error whereby the stopping condition for a loop will never be reached.


Take the following script for example, which prompts a user for a number and then alerts all of the even numbers less than or equal to it until 0.

  let num = prompt("Give me a number!");
  while (num !== 0) {
    if (num % 2 === 0) {
      alert(num);
    }
    num--;
  }

In the above script, what happens when the user-provided number is negative?!

What's a quick fix that will avoid that bug above?

Note: if you experience an infinite loop while programming: if in the browser, close the page and reopen it -- if you're alerting over and over, most browsers will give you the option to stop the page from alerting any more. If you're in the console, CTRL+C will cease the script from running.


Loop Disruption


Sometimes, however, you'd like a loop to continue iterating until some criteria is reached *inside* of the loop body.

By the time you've entered the body, however, it's too late to rely on the loop condition to stop iteration, so we have a couple extra tools to disrupt the loop from inside of it!

The break command will cease execution of the current loop, leave the loop-body, and will return the script execution to directly after the broken loop.

The continue command will cease execution of the current iteration of the loop, executes the post-loop action, and returns to the loop condition to possibly begin the next iteration.

  let nums = [1, 5, 4, 3, 2];
  
  // Prints the first even number in nums
  for (let num of nums) {
    if (num % 2 === 0) {
      alert("Hey look! Num is even: " + num);
      // [!] Breaks out of the loop for good,
      // since we don't need to look any further 
      break;
    }
  }
  let nums = [1, 5, 4, 3, 2];
  
  // Skips over odd numbers
  for (let num of nums) {
    // Odd numbers will have num % 2 === 1, which
    // is truthy!
    if (num % 2) {
      continue;
    }
    alert("Hey look! Num is even: " + num);
  }

Did we need the continue; statement above? No not really, it's just meant to show you how it operates, and how it differs from the break; statement.

And that's about all the breath I have today to talk about loops... until we circle back next time ;)



Style Tips

Always indent code by 2 spaces that is inside the body of a control flow structure (like an if-statement or loop-body).

Good Style

Bad Style

  if (x === y) {
    alert("Neat");
  }
  if (x === y) {
  alert("Neat");
  }

Always indent code by 2 spaces that is inside the body of a control flow structure (like an if-statement or while-loop).

Good Style

Bad Style

  while (x === y) {
    alert("Neat");
    x++;
  }
  if (x === y) {
  alert("Neat");
       x++;
  }

Never place a statement on the same line as a closing curly-bracket of a control flow structure (like an if-statement or while-loop).

Good Style

Bad Style

  if (x === y) {
    alert("Neat");
  }
  alert("Outside the if!");
  if (x === y) {
    alert("Neat");
  } alert("Outside the if!");
  

Don't use an if-conditional where clever expression evaluation would suffice.

Good Style

Bad Style

  alert(x === y);
  
  
  
  
  if (x === y) {
    alert(true);
  } else {
    alert(false);
  }

Do NOT put statements on the same line as a curly bracket* (* see directly after this for an exception).

Good Style

Bad Style

  if (x === 5) {
    alert("Yay! High five!");
  }
  if (x === 5) {alert("Yay! High five!");
  }
  
  for (let i = 1; i < 5; i++) {
    alert(i);
  }
  alert("Yay! Done!");
  for (let i = 1; i < 5; i++) {
    alert(i);
  } alert("Yay! Done!");
  

Note, there are exceptions to this guideline; for example, you'll often see programmers put a single-statement body on one line.

Good Style

OK Style

  if (x === 5) {
    alert("Yay! High five!");
  }
  if (x === 5) { alert("Yay! High five!"); }
  
  
  for (let i = 1; i < 5; i++) {
    alert(i);
  }
  for (let i = 1; i < 5; i++) { alert(i); }
  
  

There will be many other stylistic tips throughout the course -- make sure you pay attention to them, style counts on assignments (and life, of course)!



  PDF / Print