Control constructs

We are now about to leave the realm of pure expressions.

Whereas expressions always evaluate to a value, there are constructs in Javascript that exist to control the flow of our program but which do not themselves produce values.

The “flow” of our program is just a way of talking about what happens when.

Normal flow of control

The normal flow of control within a function is each statement (roughly speaking, a line) executing in order.

console.log('Hello.');
console.log('How are you doing?');

When this code runs, the first line runs first, printing ‘Hello.’ to the console and then the second line runs, printing ‘How are you doing?’.

There are three fundamental kinds of control constructs

Calling and returning from a function.

Conditional execution: either run some code or don’t based on some condition.

Looping: run some code repeatedly, while some condition holds.

Function calls change the flow of control

When we call a function we leave the normal flow to jump to the code in the function we are calling.

The function’s code is executed and then when it returns, we come back to where the function was called and resume normal flow.

return

You’ve already seen return as it is a key part of defining functions.

const double => (x) => {
  return x * 2;
};

The return causes the function it is in to return the value of the following expression to whatever code called the function.

This is a call to double

const x = double(16);

The value of that call is whatever is return’d from the function when called with the argument 16.

That value is then assigned to the variable x.

Statements vs expressions

The line return x * 2 is not an expression because it does not evaluate to a value. For instance we can’t say:

let foo = 10 + (return x * 2); // ☠️☠️☠️

Javascript will complain, probably something like ‘Unexpected token return’ which is just a way of saying: this doesn’t belong here.

return statement

Instead we describe it as a “return statement”.

As in, “Your function needs a return statement.”

The return is a control flow statement because it changes the flow of what code executes next.

const foo = () => {
  return 10;
  console.log('hello'); // this line never runs
};

if

The main way to conditionally execute some code is with an if statement.

if (x > 10) {
  console.log('x is really big!');
}

The code inside the {}s only runs if the expression inside the ()s evaluates to true.

Structure of an if

if (condition) { body }

else

Sometimes we want to choose between two options:

if (x > 10) {
  console.log('x is really big!');
} else {
  console.log('x is small.');
}

The code in the {}s after the else is called the else clause or else branch and it runs if the condition is false.

Multi-way choices

If we wanted to make a three-way choice, we could write this:

if (x > 10) {
  console.log('x is really big!');
} else {
  // In here we know x is <= 10
  if (x > 5) {
    console.log('x is kinda medium');
  } else {
    console.log('x is small.');
  }
}

The outer if has two branches: one where x is greater than 10 and one for when it’s not, i.e. when it’s less than or equal to 10.

The second if, inside the else branch of the first if, branches into an x > 5 path and an everything else path.

So there are actually three possible branches

Normally we’d write that like this

if (x > 10) {
  console.log('x is really big!');
} else if (x > 5) {
  console.log('x is kinda medium');
} else {
  console.log('x is small.');
}

Same logic, just formatted slightly differently.

Looping

Loops provide a way to execute some code repeatedly, usually until some condition is met.

Two main kinds of loops

while

for

A side quest: mutable variables

Most of the variables we’ve been dealing with (function arguments or variables defined with const) have been used as names for unchanging values.

However, for a loop to only repeat until some condition is met, it must be possible for conditions to change.

That usually means that some variable’s value is going to have to change.

let

As was mentioned briefly in the Variables slides, variables declared with let rather than const can be assigned new values after they are first defined.

let x = 10;

// some time later
x = 20;

We say that second line assigns the value 20 to x.

Any expression can go on the right-hand side of the =.

In particular …

x = x + 1

That looks funny as math because no number is equal to itself plus one.

But in Javascript = means, evaluate the expression on the right hand side of the = and then assign that value to the variable on the left hand side.

x + 1 will evaluate to the number one greater than the current value of x and then that new value will be assigned to x.

x++

Because it is so common to want to increment a variable by one, there is a extra-concise shortcut for doing that, shown above.

It is frequently used in loops.

End of side quest

while

while loops do what the name suggests: loop until some condition is true.

let x = 0;
while (x < 100) {
  console.log(x);
  x++;
}

You can read this as, “While x is less than 100 console.log it and then add 1 to it”.

The expression in the ()s is evaluated and if it evaluates to true then the body of the while—the code between the {}s—is executed.

After executing the body, control jumps to the top of the loop, checking the condition again and executing the body as long as the condition is true.

Then normal flow resumes immediately after the loop.

for

for loops capture a common pattern of looping.

In fact the while loop from two slides ago is an example of that pattern.

The pattern

First we initialize some variable: let x = 0;

Then we loop while some expression involving that variable is true: x < 100

In the loop body we do whatever and then update the variable: x++

for loop equivalent

This for loop is equivalent to the earlier while loop.

for (let x = 0; x < 100; x++) {
  // do whatever.
}

for loop structure

for (initializer; condition; updater) { body; }

Initializer

The initializer runs once at the start of the loop.

It usually defines a variable, called the loop variable, that will control the loop.

Condition

The condition is evaluated at the start of each time through the loop.

When it evaluates to false, the loop immediately ends.

Otherwise the body of the loop executes once before possibly looping again.

The condition almost always involves the loop variable.

Updater

The updater runs immediately after the body of the loop and before the condition is checked again.

It almost always updates the loop variable in some way that potentially changes the result of evaluating the condition.

Body

The code that runs each iteration of the loop.

Often uses the loop variable but should not update it.

(Leave that to the updater.)

A classic loop

for (let i = 0; i < s.length; i++) {
  // Do something with s[i], e.g.
  console.log(s[i])
}

You will eventually be able to write this loop in your sleep.

Let’s take it apart.

The initializer

let i = 0

Declare the loop variable and set it to zero.

In loops like this the loop variable is almost always called i, short for “index” since that’s how it’s going to be used.

The condition

i < s.length

We want to execute the loop body once for each valid index in the string.

Which we know go from 0 (where we initialized i) up to but not including s.length.

The updater

i++

Just increment (i.e. add one to) i so it takes on all the values from 0 up to s.length.

The last update will set i to s.length which will cause the condition, i < s.length, to become false, ending the loop.

The body

console.log(s[i]);

In this case we’re just printing out each character.

Because each time we execute the body i will have a different value, s[i] will extract a different character from s.

Nested loops

If we need to work in two dimensions we may need to nest loops.

Basic structure of nested loops

for (let i = 0; i < n; i++) {
  for (let j = 0; j < m; j++) {
    // code that refers to i and j
  }
}

The body of the inner loop will execute n * m times.

The two loop variables need to have different names or the inner loop’s variable will hide the outer loop variable and we won’t be able to refer to it.

🤓 Extra Nerdy stuff 🤓

The following slides contain some extra information you don’t really need to worry about.

But if you’re interested in knowing the whole truth you might want to read them.

Otherwise, feel free to skip to the last slide.

🤓 Nerd topic 1 🤓

Some extra details about if

The body of an if statement

The actual rules of Javascript allow either a single statement or a block contained in {}s after the condition of an if or after an else in an if/else.

So this is legal:

if (x > 10)
  console.log('x is really big!');

and is equivalent to this version with braces:

if (x > 10) {
  console.log('x is really big!');
}

However …

We should always use braces.

Here’s why.

Suppose we have this:

if (x > 10)
  x = 10;

Perfectly legal. Clamps x to be no greater than 10.

Then suppose we want to add some output, so we add a line like this:

if (x > 10)
  console.log('Clamping x to 10');
  x = 10;

Do you see the problem?

Despite the indentation that code is equivalent to this:

if (x > 10) {
  console.log('Clamping x to 10');
}
x = 10;

I.e. x now always gets set to 10 because the assignment isn’t in the body of the if any more.

If we had written it like this from he beginning

if (x > 10) {
  x = 10;
}

Then when we added the line it would turn into this:

if (x > 10) {
  console.log('Clamping x to 10');
  x = 10;
}

And would work the way we intended.

Programming is hard enough

Don’t make it harder.

We should adopt coding practices that make it easier to write correct code.

Every little bit helps.

But!

If you were really, really paying attention you might notice that there is a place where I advised leaving out the braces.

if (x > 10) {
  console.log('x is really big!');
} else if (x > 5) {
  console.log('x is kinda medium');
} else {
  console.log('x is small.');
}

That could also be formatted like this

if (x > 10) {
  console.log('x is really big!');
} else
  if (x > 5) {
    console.log('x is kinda medium');
  } else {
    console.log('x is small.');
  }

The second if/else counts as a single statement and thus the whole body of the first else branch.

🤓 Nerd topic 2 🤓

Two kinds of functions with no return statement.

Shorthand functions

In the Functions slides you saw a shorthand form:

const double = (n) => n * 2;

Which is a more concise way of writing:

const double = (n) => {
  return n * 2;
}

It just saves us from having to write the {}s and the return when the body of the function is a single expression whose value we want to return.

Functions we call for side effects

This function also has no return

const drawTriangle = (x1, y1, x2, y2, x3, y3) => {
  drawLine(x1, y1, x2, y2, 'blue');
  drawLine(x2, y2, x3, y3, 'blue');
  drawLine(x3, y3, x1, y1, 'blue');
};

When does it return?

And what value does it return to its caller?

This is an example of a function that we call for the side effects it has (drawing lines on the screen), not for the value it computes.

It returns when the normal flow gets to the end of the function body.

And it returns the special value undefined.

Which doesn’t usually matter because the code that called this function probably isn’t doing anything with the return value anyway.

I.e. it is equivalent to this

const drawTriangle = (x1, y1, x2, y2, x3, y3) => {
  drawLine(x1, y1, x2, y2, 'blue');
  drawLine(x2, y2, x3, y3, 'blue');
  drawLine(x3, y3, x1, y1, 'blue');
  return undefined;
};

So it's true all functions return a value

But it may be the special value undefined which isn’t useful for much of anything.

And there may be no explicit return in the code of the function.

🤓 End of nerding out 🤓

Up next

For loop patterns