Decisions affect what parts of our code execute at run-time. They are the way we are able, as developers, to choose what we want our program to do. All of this can be accomplished with the humble if statement.
The first thing to understand about the if statement is its grammar [TODO: Link]. The basic grammar looks like this.
if(conditionalExpression)
statementOrStatementBlock// true side
else
statementOrStatementBlock// false side
There are two keywords in this grammar.
The if keyword tells the computer we’re about to make a true/false decision based on some information (the conditionalExpression). The statementOrStatementBlock will be executed only if the conditional expression produces a true boolean result.
The else keyword tells the computer what to do should that conditional expression turns out to produce a false result. Note that the else portion with its statementOrStatementBlock is entirely optional.
Besides the keywords, we have these placeholder parts of the grammar.
The conditionalExpression is simply any expression that can produce a true or false value.
By the way, the words true and false are JavaScript keywords.
A statementOrStatementBlock is either a single statement or a single statement block (a block is zero or more statements inside curly braces - { }).
A single statement can be on its own line.
A statement block should have the opening curly brace at then end of the same line as the initial statement and the closing curly brace on its own line.
The following is an example of a statement block with properly positioned curly braces.
Sample Code
if(quantity===0) {
// zero or more lines of code
} else {
// zero or more lines of code
}
It’s important to remember that this if-else structure provides alternate paths of logic. If one path is followed, then the other is not followed.
Let’s explore the If/Else statement with a simple example.
To begin, create a file called grok-decisions.js. Then run it in the terminal with node --watch grok-decisions.js.
Let’s add some code to portray using a self-serve gas station where we want to fuel up our vehicle.
grok-decisions.js
let bankBalance = 78.23;
let preAuthorizationAmount = 50.00;
if(bankBalance>preAuthorizationAmount) {
console.log('Transaction Approved.');
console.log('Please Remove Card Now and Lift Nozzle');
}
We’ve hard-coded values in our program, and you can probably guess what is going to be displayed on the screen. Because the bank balance is the larger of the two values, then the conditional expression bankBalance > preAuthorizationAmount results in a true, and then the code inside statement block is executed.
If you’re using the debugger, try placing a breakpoint on the if statement and then step through the code.
Let’s edit the preAuthorizationAmount and increase it to $100.
grok-decisions.js
let preAuthorizationAmount = 50.00;
let preAuthorizationAmount = 100.00;
Now, because 78.23is∗not∗greaterthan100.0, the code execution jumps past the instructions on the “true” side of the if statement, and nothing is displayed on the screen.
Let’s add an else side to our if statement.
grok-decisions.js
if(bankBalance>preAuthorizationAmount) {
console.log('Transaction Approved');
console.log('Please Remove Card Now and Lift Nozzle');
}
} else {
console.log('Transaction Declined.');
console.log('Please Remove Your Card').;
}
Play with making changes to either the bankBalance or the preAuthorizationAmount and observe the effects. Can you control which output is generated by modifying the values of these variables?
With this simple example, a whole world of possibilities begins to open up for us as programmers. But to really grasp the possibilities, we need to add more decisions and explore more conditioal expressions.
So far, we’ve created two alternate paths of logic by adding a single if statement. Let’s see what else we can compare, and what that might mean for the total number of logical paths available to our program.
Modify the grok-decisions.js to reset the preAuthorizationAmount to $50. Then add some code to track the fuel purchase and update your bank balance.
grok-decisions.js
let bankBalance = 78.23;
let preAuthorizationAmount = 100.00;
let preAuthorizationAmount = 50.00;
let actualFuelCost;
if(bankBalance>preAuthorizationAmount) {
console.log('Transaction Approved.');
console.log('Please Remove Card Now and Lift Nozzle');
Let’s enhance the self-serve gas station experience. Often, there’s an option to choose a car wash. Let’s weave that into our existing code.
grok-decisions.js
let bankBalance = 78.23;
let preAuthorizationAmount = 50.00;
let carWashAmount = 0;
let actualFuelCost;
let includeCarWash = true;
if(includeCarWash==true) {
carWashAmount=19.99;
}
// remaining code is unchanged...
Because we have given our carWashAmount an initial value of 0, we don’t have to have an else block on our new IF statement.
Notice how the if uses includeCarWash == true as the conditional expression. Earlier, includeCarWash was given a value of true, and therefore it’s data type is boolean. Since all that a conditional expression needs is a boolean result, it’s redundant to use includeCarWash == true. Let’s fix that.
grok-decisions.js
if(includeCarWash==true) {
if(includeCarWash) {
carWashAmount=19.99;
}
This is much cleaner. By the way, I was careful in choosing the name includeCarWash for our variable, because it reads smoothly for us as humans:
“If we include the car wash…”
We’ll fill out the rest of this with a receipt of all the charges. We’ll also reset the car wash amount if the pre-authorization fails.
grok-decisions.js
if(bankBalance>preAuthorizationAmount) {
console.log('Transaction Approved.');
console.log('Please Remove Card Now and Lift Nozzle');
actualFuelCost=23.98;
} else {
console.log('Transaction Declined.');
console.log('Please Remove Your Card');
actualFuelCost=0;
carWashAmount=0;
}
// We'll be using these more in some later examples
console.log(`Your new balance is: $ ${bankBalance}`);
⚗️ Try some experimentation. Try editing the values for includeCarWash and preAuthorizationAmount in various combinations. Because we have two decision statements stacked on top of each other, how many alternating paths of logic are present in the code?
Let’s throw in some assorted combinations of the other relational operators with a few more if/else statements.
You can think of == as being a “general equality” operator, while === is a “strict equality” operator. Strict equality requires the data types to also match.
grok-decisions.js
// Can we compare strings and numbers?
const five = 5;
const digit = '5';
if(digit==five) {
message=`Looks like '${digit}' and ${five} are the same.`
console.log(message);
} else {
console.log('No, we treat them differently.');
}
if(digit===five) {
console.log('Yup, still the same');
} else {
message=`Strictly speaking, '${digit}' (a ${typeofdigit}) is not the same as ${five} (which is a ${typeoffive})`;
console.log(message);
}
The opposite of == is !=. Likewise, the opposite of === is !==.
grok-decisions.js
const seven = 7;
if(seven!=five) {
console.log('Of course 7 is not equal to 5');
}
if(seven!==seven.toString()) {
console.log('Strict equality requires the same value AND data type!');
}
We’ve seen the > operator. Our remaining operators are >=, < and <=. Have you thought about what the opposite of > is? Examine the following experiment to see if you can figure it out.
grok-decisions.js
if(seven>=7) {
console.log('\nCan you tell me which operator gives the opposite of >=?');
}
if(seven<=7) {
console.log('Can you tell me which operator gives the opposite of <=?\n');
}
There’s another little experiment that you can try. See if your ideas around “opposite” relational operators still holds.
grok-decisions.js
let number = 0; // Try different numbers...
console.log(' 0')
console.log('<---|---|---|---|---|--->');
console.log(' The Number Line');
if(number<0) {
console.log('Less than 0 |');
}
if(number>0) {
console.log(' | Greater than 0');
}
if(number===0) {
console.log(' Exactly | Zero!')
}
console.log(`The number is: ${number}`);
⚗️ Do a little more experimentation. Try different values for number and observe the results.
We can combine arithmetic, relational and boolean operations to handle complex conditional expressions. When we do that, the natural order of those operations follows this precidence.
Did you know that for most post-secondary schools in Canada, you need to be enrolled in more than three courses to be considered a full-time student?
grok-decisions.js
let courseCount = 4;
let isEnrolled = false, isFullTimeStudent;
if(courseCount>0)
isEnrolled=true;
if(isEnrolled&&courseCount>=3)
isFullTimeStudent=true;
else
isFullTimeStudent=false;
console.log(`\n${courseCount} courses. Full time? ${isFullTimeStudent}`);
Various financing options are available for students, but sometimes full-time students will find themselves eligible for special scholarships. Usually, there are some conditions that might apply…
console.log(`You are eligible for $ ${eligibleScholarship} in special funding.\n`);
Review the code above carefully. Can you see the order of operations? Part of the job of a developer is to think through whatever possible logic is required to produce the correct solution.
Hopefully you’re already aware that JavaScript is a dynamically typed language. That has a bearing on decisions because you don’t need a strictly-typed true or false in a conditional expression. JavaScript supports the notion of “truthy” and “falsy” values.
Remember the student funding we were exploring? We can test if any scholarships or bursaries are available. A non-zero value would be regarded as “truthy”.
grok-decisions.js
if(existingBursaries) {
console.log(`You have been granted $ ${existingBursaries} in additional funding.`);
}
if(eligibleScholarship) {
console.log(`You are eligible for $ ${eligibleScholarship} in additional scholarships. (Application is required)`);
}
Imagine that somewhere we have gathered information about a student. (We’ll just hard-code it for now.) We can simply check if that data exists before we proceed to process that information.
grok-decisions.js
let student;
// Hard-code for this demo
student= {
id: 19263874,
name: 'Stewart Dent'
}
// Sometime later in our code...
if(student) { // IF we have student info...
console.log('Your eligibility status has been forwarded to the registrar''s office');
} else {
console.log('Please log in to preserve your funding assessment.');
}
This could be a good opportunity to experiment with additional truthy/falsy decisions. What further examples can you create on your own?
When things go sideways...
If you’ve made it this far, then you need to consider how we might accidentally introduce “bugs” into our code that are revealed only at run-time. These are called Logical Errors. Conditional expressions are a common place where these errors occur.
Head over to the following article to discover some of the more common logical errors that can creep into our code.
Experiment with additional changes to this tutorial. For example, what would happen if you included a 5% sales tax on the price of the car wash (calculated after price is assigned to the variable)? Are there any potential “bugs” in your code? How would you find and fix those bugs?
Perhaps you would like to add descriptions of your data types as you experiment. Consider adding these functions to your script.
Extra functions for grok-decisions.js
const describeDataType = function (value) {
let result;
if (value == undefined || value == null) {
result = `The value is ${value}`;
} else {
result = `The data type is ${value.constructor.name}`;
Up to this point, we’ve relied on Simple Sequence and Conditionals (if-else) as the primary means of flow control. Those can take us a long way towards writing sophisticated code to handle complex problems, but they miss a whole other characteristic of code execution critical to most software needs: Handling Repetition.
The idea of doing certain steps over and over is a pretty simple one. A common place where we see this is in the area of mathematics.
Remember the idea of factorials? A factorial is where we follow a pattern of multiplying a number by all the numbers that come before it. For example, the factorial of five is
5!=5×4×3×2×1=120
This is an ideal example of a problem that requires repetition to produce the solution (particularly if we want to calculate the factorial of any number, not just 5).
Before we jumpt into the code, let’s take a quick look at the different ways we are able to handle repetition in JavaScript.
Just as with if-else statements, the various types of loops in JavaScript make use of conditional expressions to control the flow of execution. There difference, however, is that the alternate paths of logic for the if-else would only run once, while the alternate paths of logic for the loops can run multiple times (based on the truthiness of the conditional expression).
Let’s look at a couple of the JavaScript statements where we can perform looping.
There are many looping statements in JavaScript. The oldest, and most general-purpose, is the while statement. As a simplistic example, consider the following code. It outputs the numbers 1 through 10 to the console.
while Statement
let count = 1;
while (count<=10) {
console.log(`Item ${count}`);
count=count+1;
}
The conditional expression - (count <= 10) - controls the repetition of the code inside the curly braces. As long as that expression results in a true or truthy value, the code will repeat. Also note that the value of count is modified inside the curly braces. Each time through the loop, its value increases by one, until it ultimately holds the value 11. At that point, the conditional expression results in a false, causing the loop to exit.
Another popular looping statement is the for statement. Look again at the code for the while statement. Notice how there are three instructions that control the repetition:
let count = 1 - Creates the variable and gives it an initial value
count <= 10 - The conditional expression controlling entry to the code in the curly braces
count = count + 1 - Changes the value of the variable tested in the conditional expression
while Statement
let count = 1;
while (count<=10) {
console.log(`Item ${count}`);
count=count+1;
}
The for statement brings all three of these instructions into one place, making it easier to see what affects execution of the code in the curly braces.
for Statement
// An alternative to our while statement
for (let count = 1; count<=10; count=count+1) {
console.log(`Item ${count}`);
}
Looping structures are a powerful way to control the flow of execution of our code. These, along with conditional statements, are great candidates for use inside of functions.
Let’s explore the logic of looping by solving several types of mathematical problems.
In our first example, we’ll use the while loop to calculate the factorial of a number. The while loop is ideal for this problem because we might not know how many times we need to repeat the steps until we reach the number that we’re calculating the factorial for.
Create a file called handling-repetition.js and run it with node --watch handling-repetition.js.
handling-repetition.js
// Calculating Factorials
let number = 5; // Some number that we will use for factorials
Let’s think about the logic that we use in performing our calculations. Specifically, we need to think about which steps are the repeating steps in the calculation. As we can see, it is the multiplication of the numbers and the fact that our numbers increment (or decrement) by 1 each time.
x!=1×(x+1)×(x+2)×(x+3)⋯
The steps continue until we reach the number that we want to calculate the factorial for.
Let’s think about what’s controlling the loop: the number of times we need to multiply. We can use a counter for this. Then we can compare that counter to our original number to determine if we need to repeat the process.
handling-repetition.js
// Calculating Factorials
let number = 5;
let count = 1;
let answer = 1; // The multiplicative identity (1) is the starting point for our calculation.
Looping is really just that simple. We have a set of steps that are controlled by some condition. What we do in those steps is up to us. We can do any kind of processing we want, and we can even have multiple loops nested inside each other to handle more complex problems.
In our next example - the Fibonacci sequence - we have a different kind of problem. We want to calculate the next number in the sequence based on the previous two numbers. This is a great example of a problem that can be solved with a loop, but it requires us to keep track of more than one value as we loop through the calculations.
This time, we’ll use a for loop to handle the repetition. The for loop is ideal for this problem because we know how many times we want to repeat the steps (the number of terms in the sequence that we want to calculate).
The Fibonacci sequence is a series of numbers where each number is the sum of the two preceding ones. The sequence starts with 0 and 1, and then continues indefinitely.
F(n)=F(n−1)+F(n−2)
The first few numbers in the Fibonacci sequence are:
0,1,1,2,3,5,8,13,21,…
Add another loop to your handling-repetition.js file to calculate the Fibonacci sequence up to a certain number of terms.
handling-repetition.js
// ... previous code for factorials ...
// Calculating Fibonacci Sequence
let terms = 10; // Number of terms to calculate
let a = 0, b = 1, temp;
console.log(`Fibonacci Sequence up to ${terms} terms:`);
We mentioned before that we can have any types of statements in our looping logic. That includes making decisions with if-else statements. Let’s see how we can use a loop to find out if a given number is prime.
A prime number is a natural number greater than 1 that cannot be formed by multiplying two smaller natural numbers. The first few prime numbers are:
But how do we determine if a number is prime? The clue is built into the descrption of prime numbers: A prime number is only evenly divisible by 1 and itself.
We can again use our counting loop to go from 2 to the number we’re checking. Then, inside the loop, we can use the modulus operator (%) and check if the result is 0. If we ever encounter a case where the result is 0, we know the number is not prime.
Add another loop to your handling-repetition.js file to calculate prime numbers up to a certain limit.
handling-repetition.js
// ... previous code for factorials and Fibonacci ...
// Determining Prime Numbers
let someNumber = 87; // Is this prime?
let isPrime = true; // Assume it's prime until we find a divisor
for(let count = 2; count<someNumber; count++) {
if(someNumber%count===0) {
isPrime=false;
break;
}
}
console.log(`${someNumber} is ${isPrime?'prime':'not prime'}`);
We can take our investigation of prime numbers a step further by generating a list of prime numbers up to a certain limit. This will require us to use nested loops: an outer loop to go through the numbers we want to check, and an inner loop to check if each number is prime.
Add another loop to your handling-repetition.js file to generate a list of prime numbers up to a certain limit. We’ll break this logic down into several steps to make it easier to follow.
handling-repetition.js
// ... previous code for factorials, Fibonacci, and prime checking ...
// Generating a List of Prime Numbers
let limit = 100; // Generate primes up to this number
console.log(`Prime numbers up to ${limit}:`);
// This outer loop goes through each number from 2 to the limit
for(let num = 2; num<=limit; num++) {
let isPrime = true; // Assume the number is prime until we find a divisor
}
Inside the outer loop, we need another loop to check if the current number (num) is prime. This inner loop will go from 2 to num - 1 and check if num is divisible by any of those numbers.
handling-repetition.js
// ... previous code for factorials, Fibonacci, and prime checking ...
// Generating a List of Prime Numbers
let limit = 100; // Generate primes up to this number
console.log(`Prime numbers up to ${limit}:`);
// This outer loop goes through each number from 2 to the limit
for(let num = 2; num<=limit; num++) {
let isPrime = true;
// This inner loop checks if the current number is prime
for(let count = 2; count<num; count++) {
if(num%count===0) {
isPrime=false;
break;
}
}
}
Finally, after the inner loop, we can check if isPrime is still true. If it is, we can print the number as a prime number.
handling-repetition.js
// ... previous code for factorials, Fibonacci, and prime checking ...
// Generating a List of Prime Numbers
let limit = 100; // Generate primes up to this number
console.log(`Prime numbers up to ${limit}:`);
// This outer loop goes through each number from 2 to the limit
for(let num = 2; num<=limit; num++) {
let isPrime = true;
// This inner loop checks if the current number is prime
Our first examples of loops were pretty basic. They were designed to illustrate the concept of repetition and how we can use loops to solve problems. But there are many ways we can improve our code to make it more efficient, more readable, and more maintainable. Practice improving your loops by trying to solve the following problems:
For the Factorial example, re-write it to use the for loop instead of the while loop. Which one do you find easier to read and understand?
For the Fibonacci example, modify it to calculate the Fibonacci sequence up to a certain number (instead of a certain number of terms). This will require you to change the logic of your loop and the way you check for the stopping condition.
For determining if a number is prime, how might you optimize the loop to reduce the number of iterations?
Expand for a hint only after you try it yourself
Your first optimization could be to only loop up to half of the number you’re checking. But you can do even better than that by only looping up to the square root of the number.
Can you explain why that works? If you can, then you have a good understanding of the properties of prime numbers and how to optimize your code to check for them.