Register to get access to free programming courses with interactive exercises

Variables and repetitions Programming fundamentals

Video may be blocked due to browser extensions. In the article you will find a solution to this problem.

Important notes

Debugging: How to find and fix mistakes

It is very easy to make mistakes when you have to handle variables, change them, keep track, etc. Especially in a loop. A great way to understand what's going on is to use the simplest debugging technique — console.log. Recall, this function prints onto the screen whatever you pass to it.

For example, I'm trying to understand what's happening inside a while loop:

while (counter <= n) {
  result = result * result;
  counter = counter + 1;
}

I'm going to add console.log here:

while (counter <= n) {
  result = result * result;
  counter = counter + 1;
  console.log(result)
}

Now, each time this block of code repeats, the result variable will be printed. Let's see:

1
1
1

(I'm running the function with n equals 3)

The result in each step is 1. This is not right, the result should increase each step... Okay, so, the problem is probably in the line where result is changing. Right! I have result = result * result, but I need to multiply result by counter, not by result.

1
2
6

Now it works! I see the steps now, and the last step produces the correct answer: 3! is 6.

Don't hesitate to put console.log wherever. It's your best friend :-)

Infinite loops

Since a while loop simply checks the condition, we can create infinite loops if we make the condition to be always true.

while (10 > 5) {
  console.log("Ten is still larger than 5");
}

This will print "Ten is still larger than 5" until the heat death of the universe, or until you close the program, or until your computer runs out of memory, whatever comes first. Because the condition 10 > 5 is always true.

An even simpler way to make an infinite loop is while (true) { ... }. true is always true.

Sometimes your loops will be infinite, even if you didn't plan on that. This is a common problem, and it only means that inside the loop you have forgotten to change the thing that is being checked. For example, if I delete the line counter = counter + 1 from today's lesson's loop:

while (counter <= n) {
  result = result * result;
}

... I'll end up with an infinite loop: counter never changes, so if counter <= n, it is true forever.

Lesson notes

Variables are like constants, but you can change their values at any moment.

let age = 21;

age = 22;         // now age is 22
age = age + 10;   // now age is 32

Loops are repeated blocks of code. A while loop is a block repeated while some condition is true.

while (condition) {
  do_stuff;
}

Here is the factorial function with a while loop:

const factorial = (n) => {
  let counter = 1;
  let result  = 1;

  while (counter <= n) {
    result = result * counter;
    counter = counter + 1;
  }

  return result;
}

Idea: make counter = 1, then multiply result by counter repeatedly, while counting up to n (the number passed to the function). When counter becomes larger than n — stop. By then, result will be the answer.

This is iteration — defined repetition of code. Different languages have different ways to to iteration. A while loop is one of the ways JavaScript offers.

Declarative vs. Imperative

Compare a recursive factorial (from lesson 8) and a non-recursive factorial (from today):

const recursiveFactorial = (n) => {
  if (n === 1) {
    return 1;
  }
  return n * recursiveFactorial(n-1);
}

const factorial = (n) => {
  let counter = 1;
  let result  = 1;

  while (counter <= n) {
    result = result * counter;
    counter = counter + 1;
  }

  return result;
}

This recursive function is declarative — it's like a definition of factorial. It declares what factorial is.

This non-recursive iterative function is imperative — it's a description of what to do in order to find factorial. Declarative comes from Latin "clarare" — to make clear, to announce, to make a declaration. You declare — I want factorial of n to be n times factorial of n-1.

Imperative comes from latin "imperare", it means "to command". You command the steps precisely — multiply this and that while counting down and keep some numbers in mind.

Declarative is what. Imperative is how.

Writing declarative code is, in general, a better idea. Your code will be easier to read and understand, and it will be easier to build upon. But sometimes you have no choice.

Many bugs come from the changing state*, and assignment statements that perform changes are often the root cause of all the evil in the universe.

So, when it comes to the assignment statement, tread lightly.

Optional reading

  • Iteration on Wikipedia
  • Fundamentals of Programming: Iteration

Lesson transcript

We are already familiar with a way of naming things using constants. For example, here is a constant pi with a value of 3.14.

const pi = 3.14;

After this line, every time we see pi we know it means 3.14. It's called a constant because, well, it's constant, permanent. After this line pi is always 3.14, it won't ever change. This is why in the paper analogy I was using a pen after all.

This might seem restrictive, but this is actually rather good. Changing the things we create is difficult to deal with. Imagine writing code using some constant and not being sure it's what you think it is.

Nevertheless, sometimes you need to be able to change things you create or change their values, in other words. Say, you want to do something 5 times. One way is to do it repeatedly while counting to 5, and then stop. For this, you'd need something to store this counter, this changing number. A constant won't work — it's permanent, you can't change its value after creating.

This is why JavaScript and most other programming languages have a concept of a "variable". Think of a variable as a piece of paper with a name written with a pen, but a value written with a pencil. At any moment you can replace the value with a different one.

We can calculate a factorial with a variable like so:

let factorial = 1;

factorial = factorial * 2;    // 2
factorial = factorial * 3;    // 6
factorial = factorial * 4;    // 24
factorial = factorial * 5;    // 120

Creating a variable is easy, it looks like a constant, but instead of const we say let. We let it be a thing, and this is not forever.

Next, we change the value of factorial. We couldn't have done this if factorial was a constant. This line means "make the factorial be whatever factorial times 2 is". So JavaScript multiplies factorial by 2 and stores this result in the factorial variable. Before factorial was 1, and now it is 2.

We repeat this 3 more times, every time multiplying the number by the next integer: by 3, by 4 and by 5. This is what you might do in your head when multiplying numbers. You probably don't think about it explicitly, but this would be a way of explaining the process of computation if you were to explain it.

This idea of using a counter to repeat something multiple times is common in programming, and most programming languages have "loops" for this. Let's look at one particular type of a loop — a "while loop". It's a block of code that is repeated while some condition is met.

Think of a farmer who works from sunrise till sunset. In other words, he works while the sun is up. You could say:

while (sun is up) {
  work
}

This is not valid JavaScript, of course, this is just to give you an idea. This "work" thing is going to be repeated again and again, while the sun is up. This means after each repetition we need to check if the sun is still up, and stop if it isn't. In other words: check, do if it's true, check, do if it's true, check... etc.

Okay, so here is the factorial function with variables and a loop instead of recursion.

const factorial = (n) => {
  let counter = 1;
  let result  = 1;

  while (counter <= n) {
    result = result * counter;
    counter = counter + 1;
  }

  return result;
}

Woah, what's going on here? First, we create two variables: one for the counter, to count from 1 to the upper limit, and the second — for the running result.

Then the main part begins — a while loop that repeats while the counter is less than or equal to n — the number passed to this function. The code that is repeated is simple: we change the values of our two variables. Running result is multiplied by the counter, and the counter itself is incremented by 1.

At some point this condition — "counter is less than or equal to n" — will become false, so the loop will no longer be repeating and the program will continue to the next thing — return result. By then, the result will be the answer, because, during those repetitions in the loop, the result was being multiplied by 1, then by 2, then 3, etc, until n, whatever that is.

Let's see what the computer does step by step when we call factorial of 3.

  1. Take one argument — number 3, known as n inside
  2. Create a variable counter, set its value to 1
  3. Create a variable result, set its value to 1
  4. Check: counter is 1, it's less than or equal to n, so
  5. Multiply result by counter and put the answer — 1 — into result
  6. Add 1 to counter and put the answer — 2 — into counter
  7. Go back and check: counter is 2, it's less than or equal to n, so
  8. Multiply result by counter and put the answer — 2 — into result
  9. Add 1 to counter and put the answer — 3 — into counter
  10. Go back and check: counter is 3, it's less than or equal to n, so
  11. Multiply result by counter and put the answer — 6 — into result
  12. Add 1 to counter and put the answer — 4 — into counter
  13. Go back and check: counter is 4, it's not less than or equal to n, so don't repeat anymore and continue to the next line in the program
  14. Return result — 6

Computer does this billion times faster but does the same thing. This kind of defined repetition is called an "iteration" in general. Our program uses iteration to calculate a factorial.

Last time we've seen an iterative process with recursion, and this time — an iteraritve process without recursion.

Both use the technique of iteration, but with recursive calls, we didn't have to change the values, we just passed the new values to the next function call. On the other hand, this factorial function doesn't have any recursive function calls, so all the transformations should happen inside a single instance, a single function box. We have no other choice but to change the values of things.

This is the first time in our course when we actually change the values of things. We haven't done this before but we were still able to compute.

The style of programming you've seen before this lesson is called "declarative". The style of programming you've seen today, with changing values, is called "imperative".

Compare a recursive factorial and a non-recursive factorial:

const recursiveFactorial = (n) => {
  if (n === 1) {
    return 1;
  }
  return n * recursiveFactorial(n-1);
}

const factorial = (n) => {
  let counter = 1;
  let result  = 1;

  while (counter <= n) {
    result = result * counter;
    counter = counter + 1;
  }

  return result;
}

This recursive function is declarative — it's like a definition of factorial. It declares what factorial is.

This non-recursive iterative function is imperative — it's a description of what to do in order to find factorial.

Declarative comes from Latin "clarare" — to make clear, to announce, to make a declaration. You declare — I want factorial of n to be n times factorial of n-1.

Imperative comes from latin "imperare", it means "to command". You command the steps precisely — multiply this and that while counting down and keep some numbers in mind.

Declarative is what. Imperative is how.

Writing declarative code is, in general, a better idea. Your code will be easier to read and understand, and it will be easier to build upon. Some languages tend to push you towards one side or the other, and some even leave you no choice, but ultimately you have to learn to see when imperative approach gives you more problems than solutions. Keeping track of changing variables is hard, and even a handful of variables make the system too complex to think about. Many bugs come from the changing state, and assignment statements that perform changes are often the root cause of all the evil in the universe.

Credits

Music: http://www.bensound.com


Are there any more questions? Ask them in the Discussion section.

The Hexlet support team or other students will answer you.

About Hexlet learning process

Sign up

Programming courses for beginners and experienced developers. Start training for free

  • 130 courses, 2000+ hours of theory
  • 1000 practical tasks in a browser
  • 360 000 students
By sending this form, you agree to our Personal Policy and Service Conditions

Our graduates work in companies:

<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.bookmate">Bookmate</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.healthsamurai">Healthsamurai</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.dualboot">Dualboot</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.abbyy">Abbyy</span>

Use Hexlet to the fullest extent!

  • Ask questions about the lesson
  • Test your knowledge in quizzes
  • Practice in your browser
  • Track your progress

Sign up or sign in

By sending this form, you agree to our Personal Policy and Service Conditions