Complete Intro Programming
Scopes Loops

Scopes and Loops

To understand scopes, we first need to understand variables.

Variables

We can declare variables in JavaScript in many ways:

let varName = 'someValue';   // Good

const varName = 'someValue'; // Better!

var varName = 'someValue';
// Avoid this one (explained later in this article)

Variables are used to represent many things, but they are famously known to hold what we call state. State is basically data that can be changed during the lifetime of an application.

In some programming languages, when we declare variables we also declare the type of data these variables are supposed to hold. Would the variable hold an integer or a string? Or an array of integers? This decreases the flexibility of what you can do with the variable, but it also decreases the possibility of using the variable incorrectly (among many other benefits).

For example, in C++ you would see code like this:

int num;

bool isPrime = true;

The int and bool keywords are equivalent to JavaScript’s let keyword, except they define variables that can only hold certain types. The variable num can only hold integers and the variable isPrime can only hold Boolean values. This is enforced by the language, so if you use the wrong data type with that variable you get an error (before you even run the program).

JavaScript does not do type checking like this. Variables declared with JavaScript can have any type of data:

let mystery;

mystery = 'Hello';

mystery = 42;

mystery = false;

mystery = [2, 3, 5, 7];

mystery = function x () {};

mystery = {};

// All ok

let vs. const

The difference between let and const is an important one to understand.

Using the keyword const creates a constant reference for the variable. We cannot change the value of a constant reference. If we put a primitive value in a constant, that value will be protected from getting changed:

const PI = 3.141592653589793;
PI = 42; // SyntaxError: "PI" is read-only

Note that if the constant is an object we can still change the properties of that object, so be careful about that:

const dessert = { type: 'pie' };

dessert.type = 'pudding'; // Sure thing

console.log(dessert.type); // pudding

However, we cannot re-assign an object declared with const:

const dessert = { type: 'pie' };

dessert = { type: 'cake' };
// SyntaxError: "dessert" is read-only

Since const is protecting a reference to something, when using the const keyword to declare a variable we must specify that variable’s value (while with let we can omit the initial value). You simply cannot declare an empty variable with const:

const xyz;
// SyntaxError: Missing initializer in const declaration

let xyz;
// Sure, No Problem. xyz is now declared but undefined.

Constants are popularly used when importing things from other libraries so that they are not accidentally changed. Constants are also great to use when defining functions because we rarely need to update a function after we define it the first time.

In general, I think it is good to always use const for your declarations and only switch to let if you absolutely need to. With const, you get the peace of mind that no mutating reassignments are happening on a variable, while with let you will have to read the code to verify that:

let answer = 42;

// some code ...

// answer MIGHT be 42 here, read the code to be sure.

Vs.

const answer = 42;

// some code ...

// answer IS STILL 42 here, no matter what happens above

The difference between let/const and var is good to understand so that you avoid using the var keyword because, well, it is weird. However, to understand that difference we need to understand the concept of scopes.

Scopes

A scope in JavaScript is a section in the code which can have its own private variables.

The simplest scope can be created using the curly brackets syntax: {}.

Here is an example scope that defines the variable answer. If you attempt to access that variable outside of that scope, you get an error:

{
  let answer = 42;
}

console.log(answer);
// ReferenceError: answer is not defined

We call scopes that are created using just the curly brackets block scopes. You have seen an example of block scopes when we talked about if-statements.

Every if-statement gets its own block scope:

if (condition) {

  // block scope

}

Another famous statement that gets its own block scope is the for-statement (explained later in this article).

Block scopes can be nested:

{
  let a = 1;

  {
    let b = 2;
  }

  // You cannot access b here, but a is ok
}

// You cannot access a or b here.

Do not confuse block scopes with empty object literals. The curly brackets syntax can also be used to define objects:

let a = {}; // empty object literal

{ let a; } // undefined object in a block scope

if (3 == '3') {
  // block scope for the if-statement
}

There is another different type of scope: function scopes.

A function scope is created for every function we invoke (and they can be nested too):

function iHaveScope() {
  // local function scope

  function iHaveNestedScope() {
    // nested local function scope
  }
}

We often identify function and block scopes as local scopes and identify the top-level scope as the global scope. When you define a variable in JavaScript that is not in any scope, you will be using the global scope. Try to avoid doing that.

In reality, it is hard to completely avoid the global scope, but you should minimize the use of any global variables because they represent a state and having that defined globally makes it more vulnerable to conflicts and data corruption.

Since every function gets its own scope, we can wrap any code in a function to force it to not use the global scope. Then, we can simply invoke the function right away because otherwise the wrapped code would not be executed. This is a common pattern in JavaScript that is called: Immediately Invoked Function Expression (IIFE):

(function() {

  // your code here

})();

Note how we needed to wrap the function in parenthesis to accomplish that. We then just called that wrapped function.

The var keyword behaves differently in function scopes and block scopes. A variable declared with var in a function scope cannot be accessed outside that function scope:

function iHaveScope() {
  var secret = 42;
}

secret;
// ReferenceError: secret is not defined (in this scope)

A variable declared with var in a block scope is available outside of that block scope. This is the weird part:

if (true) {
  var i = 42;
}

console.log(i); // 42

The i variable that we used in the if-statement above will continue to exist beyond the scope of that statement. This does not really make sense.

It is why this article started with the advice of avoiding the var keyword. Just use const if you do not need to change the reference to the created variable (most cases) and use let if you do (some cases).

Loops

Sometimes you need to repeat actions and computers are very good at that. If you try to punish a computer into writing the phrase “I will not freeze unexpectedly” 20 times, they can do that in milliseconds. All they need is what programmers call a loop.

A loop can be written in one of many ways. The most common form of a loop is using the for-statement. Here is the for-statement that a computer can use to write the punishment phrase 20 times:

for (let i = 0; i < 20; i++) {
  console.log('I will not freeze unexpectedly');
}

While the for-statement above might be easy to understand, to be able to write for-statements yourself you should understand the three different sections that I wrote inside the for-statement parenthesis. They are separated by semicolons.

Here are some labels we can give to these sections:

for (
  do-once-before-everything; // 1st
  check-condition-before-every-iteration; // 2nd
  do-after-every-iteration // 3rd
) {
  // A single iteration statements
}

We call the body of the for-statement an iteration. A for-statement will do zero or more iterations based on the three sections in its parenthesis.

The for-statement also gets its own block scope. All variables we declare with let and const inside a for-statement (including inside its parenthesis) are only accessible within that for-statement.

Before the first iteration, the for-statement executes the first section in its parenthesis. It will do that just once (not before every iteration). In the example above, we declared the variable i (representing an index) and set its initial value to 0. We can initialize many variables if we need to inside this first section. We can also omit this section if we want the for-statement to use other variables that already exist in its parent scope.

Now that we have the index variable set to 0, the for-statement will attempt its first iteration. Before that iteration (and every other iteration), it will check if the condition expression that we supply in the second section is true. If it is true, the iteration will be carried out. If that condition is false, the for-statement will be done and the computer will continue to execute whatever is after the for-statement.

After every iteration is done, the for-statement executes the third section in its parenthesis. Usually, this section is used to increment (or decrement) the loop’s index.

I used the increment operator (++) to do that because it is commonly used in that section. The increment operator is equivalent to adding 1 to its operand. The following three expressions all add 1 to i:

let i = 0;

i++;        // i is now 1

i = i + 1;  // i is now 2

i+=1;       // i is now 3

The last expression uses what we call a compound assignment. It is a special syntax for the assignment operator that is combined with the + operator (two birds with one stone). The compound assignment/plus operator will do the plus operation first and then assign the new value to its operand. Just like +=, there is also *=, -=, and /=. Not all operators can be combined in JavaScript. For example, you cannot do &&= or ||= (although other languages allow that).

It is usually a good idea to avoid increment operators ++ (and decrement operators --) when possible because they tend to make the code less readable. However, they are fine in a for-statement.

While the third for-statement section can be used for this purpose, nothing prevents us from using something similar to the last statement inside the for-statement body.

The example above can be written without using the first or third section of the for-statement this way:

let i=0;
for (;i < 20;) {
  console.log('I will not freeze unexpectedly');
  i++;
}

Note how I left the first and third sections above empty. We can do that.

A Practical Example For Loops

Being able to execute the same group of statements many times is neat, but what is a practical example for that?

If I ask you to sum all the numbers under 100, basically 1 + 2 + 3 + 4 + … + 99, how would you do that?

If you are into math, you might immediately say: there is a formula for that.

There is, but what if you do not know that formula and all you can use is JavaScript? A loop would do that. Loop exactly 99 times and add the loop index to a running sum. Do you think you can attempt that on your own first? (the answer is 100*99/2, or 4950).

Here is one loop you can use to calculate that sum:

let sum = 0;
for (let i = 99; i > 0; i--) {
  sum += i;
}

console.log(sum);

Note how I made the loop index start from 99 and go all the way down to 0. I also used the decrement operator in the third for-statement section.

Also note how I maintained the sum variable inside the parent scope of the for-statement so I can access it inside and outside the for-statement.

The iteration that was repeated 99 times simply adds the loop index to the sum variable each time, resulting in 99 + 98 + 97 + …​ + 1.

break/continue

Sometimes it is useful to skip one iteration while executing a for-statement. We can use the continue keyword to do so.

Other times it is useful to finish the for-statement early, before its condition turns into false. We can use the break keyword to do so.

The break/continue keywords are usually used with an if-statement. While the for-statement is doing its iterations, if a certain condition is true we can either skip the current iteration or break out of the whole loop.

Examples:

1 - Write code to repeat the 99 numbers sum example above but only include numbers that are divisible by 2 and 3. So 6 and 12 should be counted but 5 and 20 should not.

Can you try to do that on your own first? (the answer is 816).

Here is one way to solve this challenge:

let sum = 0;
for (let i = 99; i > 0; i--) {
  if (i % 2 !== 0 || i % 3 !== 0) {
    continue;
  }
  sum += i;
}
console.log(sum);

Note how I used a condition to skip every iteration when the number is not divisible by 2 and 3. This is not the only way to solve this problem and it is certainly not the most efficient way to do so. Usually, we use continue and break when the condition that controls them is unknown.

For example, we can use them when the condition depends on some sort of user input.

The easiest way to take input from the user in a browser environment is to use the special prompt function. It will show a prompt and ask you to type something. Whatever you type will be captured as the return value of the function. Try it below by uncommenting out the last line:

Note how console.log can have multiple arguments!

2 - Write code to keep prompting the user for an answer until they enter the right answer, 42! Note that prompt always return a string.

for(let answer;;) {

  answer = prompt('What is your answer?');

  if (answer === '42') {
    break;
  }

}

Note how I only used the first section of the for-statement to declare an answer variable (I did not need to initialize it). I did not need the other two sections because I used a break statement to determine when to exit from the loop.

This loop can run for an infinite amount of iterations until the user types in 42.

If a loop has no way to exit at all, we call it an infinite loop. Those will just keep running until they freeze your computer (by using all its resources).

Here is the simplest infinite loop (do not execute this code):

for (;;) {}

There is no initialization, condition, or increment. It just runs forever. You will probably never need to use that. Just be aware that you can accidentally run into that.

for…​in and for…​of

The good old for-statement has two cousins. A for…in statement and a for…of statement. Those are special forms that are designed to work with variables that represent collections like arrays, objects, or maps.

The for…​in statement is weird and might surprise you, so I recommend that you just avoid using it. The for…​of statement, on the other hand, is not too bad at all. Here is an example of how we can use it to iterate over an array:

const primesArr = [2, 3, 5, 7];

let sum = 0;

for (let prime of primesArr) {
  sum += prime;
}

console.log(sum); // 17

Compare that to what you would need to do in order to write the exact same example with a regular for-statement:

const primesArr = [2, 3, 5, 7];
let sum = 0;

for (let i = 0; i < primesArr.length; i++) {
  sum += primesArr[i];
}

console.log(sum); // 17

The bolded parts above are what is different. Clearly, the for…of statement makes this task much easier. With for…of you do not need to manually manage a loop index.

while and do-while

There are two more statements that we can use to create a loop.

There is the while-statement, which is basically a for-statement without the first and third section:

while (check-condition-before-every-iteration) {

  // A single iteration statements here...

}

I prefer while-statements over for-statements because I find them to be more readable. We do not need to rely on a weird structure (first, second, third) to write them. They are much closer to how we describe things in English.

In English:
Keep sleeping while it is dark outside.
In JavaScript:
while (isDarkOutside) {
  keepSleeping();
}

Anything you can do with a for-statement you can do with a while-statement as well. Here is the 99-sum example written with while:

let sum = 0;
let i = 99;
while (i > 0) {
 sum += i;
 i -= 1;
}
console.log(sum);

Admittedly, this solution has a bit more lines of code than the one we did with a for-statement, but I find it to be easier to parse with the human eye.

My recommendation is to understand the for-statement structure but use the while-statement when you need simple loops.

Some loops have the special need of being evaluated at least once. In such loops,the iteration statements are to be executed before checking the loop condition. The perfect example of that is the prompt example we wrote above. We KNOW that we need to read the user’s answer at least once.

We can use a do…while statement to create such loops. Here is the prompt example written with do…while:

let answer;

do {
  answer = prompt('What is your answer?');
} while (answer !== '42');

I find this also to be a lot more readable than using a for-statement with empty sections. It is very clear. It does what is in the body of the statement at least once and then starts checking the while condition. It then keeps re-doing what is in the body of the statement while the condition is true.

Practice Challenges

Do the following exercises right now. There are easy!

1 - Figure out the output of the following code:

{
  let a = 1, b = 2, c = 3;

  {
    let b = 10;
    {
      c = 20;
    }
  }

  console.log( a + b + c );
}

2 - Given an array of printable ASCII numbers in decimal, write a function to convert that array into a string. For example, use a space in the string when the number is 32, A when it is 65, and Z when it is 90.

Examples of expected output:

asciiArrToString([72, 101, 108, 108, 111, 33]);
// "Hello!"

asciiArrToString([52, 50]);
// "42"

Hints:

  • You can use the function String.fromCharCode to convert a single ASCII number into its corresponding printable characters

  • You need a loop