loading...
Cover image for What The For Loop?

What The For Loop?

yechielk profile image Yechiel Kalmenson Originally published at blog.yechiel.me on ・4 min read

Learning From A Popular Interview Question

This is another post in the genre of technical interview questions I’ve come across during my job hunt, and how to go about solving them (For more posts see here, here, and here).

Today’s question is a simple one, but the correct answer gets to the basics of a very popular feature in most languages; so popular, we almost never give it any thought.

The question is the following: write a function that prints all the odd numbers up to 20.

Sounds simple, right? Any beginner will tell you that this is a job for a for loop. So let’s write it out:

for(let i=0; i<=20; i++){
  if(i%2===1){
    console.log(i)
  }
} 

In (over)simplified English: we run a loop for 20 iterations, during each iteration of the loop we check to see if the value of a variable i is odd and if it is we print it to the console.

The above function satisfies the requirements. If you run it in your console, you will see it does the job. The question is, is there a better way to do things?

What The For Loop?

Note: for more information about JavaScript for loops see the documentation on W3Schools

Obviously, there is (otherwise there would be no point to this blog post); to understand how, let’s take a closer look at the syntax of the for loop, and more specifically, the first line:

for(let i=0; i<20; i++){

We can see that the for loop takes as an argument three statements separated by ;’s. Let’s take a closer look at them.

The first statement is an expression that is run only one time, before the for loop gets executed. It’s usually used to initialize a counter, but you can put any valid JavaScript expression, or none at all (for example, if you already initialized your counter outside of the for loop). In the example above, the first statement defines a variable i and sets its value to 0.

The second statement is a conditional which gets evaluated before each iteration of the loop. As long as the conditional evaluates to true the loop keeps running. Once conditions change so that the second statement evaluates to false, we break out of the loop. In our example, the conditional is i < 20 so the loop runs as long as the value of i remains below 20.

The third statement is another expression. This expression is run after every iteration of the loop. It’s usually used to increment the counter, but again, you can put any legal JavaScript in there, and it will run (of course, if you aren’t using it to increment a counter you need to make sure you have another way of changing your conditional in the second statement to true, otherwise you will be stuck with a dreaded infinite loop).

Person rolling down Escher's infinite staircase

In our previous example, we are using the third statement to increment the value of i after each iteration, so that after 20 iterations i is equal to 20, i < 20 evaluates to true, and we break out of the loop.

We Can Do Better

Now let’s take a look at our function and see how we can optimize it.

As a refresher here is the function:

for(let i=0; i<=20; i++){
  if(i%2===1){
    console.log(i)
  }
}

So we set the value of i to zero and start the loop. At each iteration of the loop we check the current value of i, if it’s odd, we log it to the console, and then we increment i by 1 and rerun the loop until i hits 20 at which point we break the loop.

How can we optimize this?

The key is in the third statement. As mentioned earlier, nothing is forcing us to increment our counter by 1 in the third statement; we can do whatever we want. Combining that knowledge with the fact that 1 is an odd number, and that adding 2 to an odd number gives us an odd number as well and the result is a loop that only has to run half of the iterations our previous attempt used.

Try putting the following in your console and see how it runs the same:

for(let i=1; i<=20; i +=2 ){
  console.log(i)
}

The differences between this function and the previous one is that here we set the initial value of i to 1 and instead of incrementing i by one for each iteration we increment it by two (we also got rid of the if statement because we know that now i is always odd, so we just log the value of i each time without checking).

So we see how sometimes knowing how things work under the hood can help us when we want to tweak them beyond the way they are usually used.

I hope this post inspired you to sometimes delve a little deeper, even into concepts that “everyone knows”.

Happy coding!


This article has been cross-posted from my blog Rabbi On Rails.
You can read more about my coding journey there, or by following me on Twitter @yechielk

Posted on by:

yechielk profile

Yechiel Kalmenson

@yechielk

He/Him/His I'm a Software Engineer and a teacher. There's no feeling quite like the one you get when you watch someone's eyes light up learning something they didn't know.

Discussion

pic
Editor guide
 
if(i & 1)
{
    // ODD
}
else
{
    // EVEN
}

I much prefer bit checks for that sort of thing, and I think it is valuable for beginners to at least know of this option because it opens the door to the concept of bit oriented shortcuts (there are a few worth knowing). Just wanted to add my two cents. But overall really good post!

 

Why do you much prefer bit hacking for that sort of thing? Is there a performance benefit or something?

For 99.9% of applications the modulus operator would be a better choice because people understand modulus immediately, and the performance trade-off (if there is one) is negligible compared to the benefit of immediate readability. There's a good chance most people will look at your bit hack and have to Google it or at least pause and think about it for a minute to understand.

That being said, it is a neat trick!

 

I assumed that a modern compiler would take care of this anyway. So I wrote a meaningless test program in C:

#include <stdio.h>

void main() {
    int i = 3;

    if(i % 2 == 0) {
      printf("Odd\n");
    } else {
      printf("Even\n");
    }
}

I then compiled it with gcc test.c -o test and used gdb to disassemble it. And indeed, the modulo operation got replaced with a bitwise and (that's ARM assembly but I'd assume it's the same on other architectures):

and r3, r3, #1
cmp r3, #0

So definitely optimize for readability (whatever that means for you) and let your tools deal with these micro-optimizations.

oh that's cool to see, thanks for sharing that.
I found this reply post in a stackoverflow thread on the matter

Performance. jsperf.com/modulo-vs-bitwise/8 Bitwise is faster across most browsers (or, it was at the time the answer was posted, in recent Chrome's there is not much difference tbf – danwellman Jan 15 at 9:05

Sure, the example I showed uses native code, not JS. Though as the post you quoted indicates it seems JS engines are always getting smarter too, and the most recent version of the same test shows the following for newer Chrome versions:

imgur.com/a/QcxZigt

Blue is bitwise and red is modulo, which means in 2 versions there's no speed difference at all and in the 3rd modulo was actually faster.

yeah, well, I posted it to support what you were showing, not as an argument. It was more just meant to be extra information than anything else.

Oh, sorry if there was any misunderstanding, I never construed this as an argument, I just added this since the quote in your comment said "in recent Chrome's there is not much difference tbf" and I had nothing better to do so I looked at the newest version of the benchmark.

 

Yes, from what I understand the performance is better, though I think in the vast majority of cases it wouldn't matter. Here is a fun read about a real life problem solved with bitwise operations (not simple even odd stuff though...). I primarily mentioned it for the reasons I already stated, it is probably the simplest intro to bitwise operations that I know of, which opens the door for learning more about the language for a beginner. Also, if you have a "use a for loop to find all even odd values" interview problem, and you whip out that solution... Just sayin'

As for the real world webdev readability, I never really considered the &1 thing to be less readable than %2. In my case I had to google both the first time I saw them. AND I'll go as far to say, I have a hard time thinking outside of myself. What I mean by that, is I love seeing new stuff in code and learning what it is/does, so it is hard for me to think in terms of that bothering people (perhaps a weakness on my part). But I do understand the value of human readability over being clever (you can always just minify your code to make it smaller afterwards). But to me both the bitwise and modulus are an abstract symbol followed by a number, and you kinda have to learn what they do when you see them, just part of coding (a part that I enjoy).

But for the most part, I just threw it in the comments because it was a post for beginners and I thought that the target audience might enjoy it. Though I'm glad you commented on the immediate readability aspect, that is worth considering.

If you're talking optimization, having the if statement (branching) is the most expensive thing, because it requires the processor to dump and reload its entire register state, and kills off the ability for it to pipeline nicely. Getting rid of the conditional is by far the most optimal solution to this code.

 

💯

In general, I would say optimize for developer time over processor time. Developer time is so much more expensive these days... 😂

 

Lazy way:

console.log("1, 3, 5, 7, 9, 11, 13, 15, 17, 19");

(I call it utilitarian)

 

😂

Though if I asked you for all the odd numbers up to 100 or 1000 your way becomes way more tedious suddenly... 😉

 

This post is good for learning to delve deeper but risks teaching programmers to write code that tries to be too clever. Unless you're writing a library that needs to be highly optimized the original code will be preferable because the intent is much clearer. Favor clear over clever because someone is going to have to maintain that code down the line.

 

True.

That's the problem with many of these technical interviews, they seem to filter for clever solutions over clean, readable, and reusable code, which take very different skills to write.

This goal of this blog post was to help new developers acquire the skills needed to pass the interview. In real life, I would say the general rule is to optimize for developer time over processor time (depending on the situation of course), which means to write code that's easily readable and maintainable by future developers (including yourself).

 

While I agree with "favor clear over clever", there's nothing particularly clever about about a for loop that increments its counter variable by two. The C standard defines for loops as for (initial clause ; controlling expression ; post-expression) and i++ is in no way special as a post-expression.

 

I agree that there's nothing particularly clever about incrementing a counter variable by two in itself. I was just pointing out the risk of going down the clever code path. If I'm maintaining this bit of code that's part of a larger codebase I would want to see something that shows explicit intent like

for(let i=0; i<=20; i++){
  let isOdd = i%2===1
  if(isOdd){
    console.log(i)
  }
} 

There are other replies to the original post that are clearly going down the clever code path so I think it's worth mentioning.

When is code too clever and when is it overly verbose? Once again, you're preaching to the choir and I agree re optimizing for readability, but in the same vain I find unnecessary assignments like let isOdd = i%2===1 just as distracting. I guess the true moral of the story is that trivial examples are a bad basis for maintainability discussions ;-)

extract isOdd out and you can now reuse it elsewhere. It also improves the readability of the for loop.

const isOdd = n => n % 2 === 1

for (let i=0; i<=20; i++){
  if (isOdd(i) {
    console.log(i)
  }
} 

isOdd should not need to be a function

Hence "trivial examples are a bad basis for maintainability discussions". In the provided code there is no "elsewhere" so it really doesn't need to be a function at that time (YAGNI and all that, and Donald Knuth had strong opinions on this one too).

Anyway, in a language with a less poor standard library we wouldn't have to define simple functions to check an integer's parity, so I think this discussion has run its course.

Ruby has it as a method. ;-) And I think what Sean meant is exactly that: common functionality like this should be part of the core language, so people don't have to go and implement it themselves every time.

 

You have a knack for breaking down concepts and really explaining them. Love reading your blog posts. Thanks for this!

 
 

we run a loop for 20 iterations

21 iterations

 

What if you were asked to do this without a for loop? ;)

 

While loop saves the day!

 

Or better yet ... use some functional programming!

Array(20)
  .fill()
  .map((_, i) => i + 1)
  .filter((num) => num % 2 !== 0)
  .join('\n')
 

in that case, see some of the other clever comments to the post :)

 
 [print(num) for num in range(20) if num % 2 !=0]

and you're done heh #vivapython

 

But that would make for a much shorter blog post... 😉

 

You can also do

console.log( [...Array(20).keys()].filter(n => {return n%2}).join(', ') )

Probably slower tho.

 
[...Array(20).keys()].forEach((i, e) => {if(e & 1 ) console.log(e)})
 

is for loop really needed here?

(filter #(not= 0 (rem % 2)) (range 1 21))