34
$\begingroup$

So, in C-style languages, the for loop has a funny little syntax:

for (int i = 0; i < 100; i++){
     // do something
}

If we order these sections in a natural, intuitive fashion, we would get something like:

 for (1; 2; 3){
      4
 }

... which is how students seem to feel like things should be ordered. But, of course, the real execution order is 1, 2, 4, 3, 2, 4, 3, 2, 4, 3, 2, 4, 3...

What tricks have you developed for helping students make sense of for loops at the very start?

$\endgroup$
  • 8
    $\begingroup$ Actually that is legal, but is an infinite loop, with i always 1. Also: defaults of all kinds are frequent sources of error. Make your programs explicit. $\endgroup$ – Buffy Jul 13 '17 at 23:15
  • 5
    $\begingroup$ @GypsySpellweaver any or all of them can be empty, in any combination, but they all default to nothing (respectively: no initialization, no termination except by explicit break, and no post-loop-body expression). $\endgroup$ – hobbs Jul 14 '17 at 5:01
  • 2
    $\begingroup$ Can you add some specific problems your students seem to have with learning the for loop? $\endgroup$ – AnoE Jul 14 '17 at 13:43
  • 1
    $\begingroup$ @GypsySpellweaver - No, there is no default behavior to a C-style for loop. The three parts are valid C expressions that don't necessarily have anything to do with indexing a variable. The expression could read another line of input from a file and the exit test could be checking for <EOF>. It is very flexible and powerful, but you must tell it exactly what you want it to do. $\endgroup$ – MrWonderful Jul 14 '17 at 18:58
  • 2
    $\begingroup$ This is why you shouldn't teach basic programming using C. It makes it extra hard to grasp the concept itself... $\endgroup$ – vacip Jul 17 '17 at 11:00

13 Answers 13

16
$\begingroup$

I know I'm coming late to this party but this is a really good question and I think it deserves a really good treatment.

The original for loop, the one with the semicolons in it, is amazingly flexible, powerful, and confusing as all get out. It is seldom taught well, it is a very easy place for bugs to hide, and it is not going away.

To master it you must learn the 1,   2, 4, 3,   2, 4, 3 pattern the OP mentioned. You must burn it into your soul. You must burn it into the souls of your students. The question is, how?

The best way is to actually see it. To watch it happen. Unfortunately debugging tools often insist on highlighting the entire line.

enter image description here

Can you tell if i is about to be initialized, tested, or incremented here? Debugger highlighting is often useless for teaching or debugging the classic for loop.

However, there is a way around that problem. The c style languages all have a feature that lets us work around this debugger limitation. Whitespace. Whitespace is meaningless in these languages. You can take advantage of that.

So long as you're willing to explain that this:

for(int i=0; i<10; i++) {
    System.out.println(i);
}

will do the same thing as this:

for(
        int i=0; 
        i<10; 
        i++
) {
    System.out.println(i);
}

then you can show them this:

enter image description here

Do that, and rather than just explaining, you are showing them how this works, you are teaching them how to debug it themselves when they get confused by it.

Now sure the multiline style isn't popular with for loops but the students certainly don't have to turn in code that looks like that. It's just a form to use while testing because our debugging tools aren't yet smart enough to highlight only the relevant parts of a line.

I've changed the speed and rhythm I use as I click. That's no accident. I'm using that to emphasize the starting and ending parts of each iteration. Students should not only feel like they can predict this. They should feel like they can use the debugger to re-teach themselves this pattern quickly if they ever need to.

Consider a cascading style that retains column position for those more visually oriented:

enter image description here

Now that's just the start of understanding loops. Writing correct, readable, efficient loops is a whole other issue that this answer hardly does justice to.

A lot of bugs hide right here. Don't undersell how worthy this is of close study.

$\endgroup$
  • 1
    $\begingroup$ You may be late to our "party" and I haven't noticed you here before, but you are giving some great answers. I'd offer you a party hat if I had one. Oh, I do, +1. $\endgroup$ – Buffy Feb 7 '18 at 12:55
  • $\begingroup$ @Buffy hehe, thank you. This isn't my usual haunt but I can't resist a good question. $\endgroup$ – candied_orange Feb 7 '18 at 14:30
  • 2
    $\begingroup$ This just might be the most useful answer I've seen in the last 3 months. $\endgroup$ – ItamarG3 Feb 8 '18 at 13:28
52
$\begingroup$

Show then how the loop can be unfolded into a while loop:

int i = 0 // Step 1
while (i< 100) { // Step 2
  // Do something
  i++ // Step 3
}

Explain that the order is derived from that unfolding.

You could start out by only teaching while loops, forcing them to make these while loops with iterators, then showing that for loops are a condensed of those while loops with incrementing variables.

$\endgroup$
  • 8
    $\begingroup$ …and be prepared for the question "Why should we use for loops instead of while loops when they're doing equivalent things, what's their advantage?" $\endgroup$ – Bergi Jul 14 '17 at 1:34
  • 2
    $\begingroup$ @Bergi You're telling the compiler what you want more clearly, allowing more optimization, and they simply take up less space and look neater. $\endgroup$ – thesecretmaster Jul 14 '17 at 1:35
  • 19
    $\begingroup$ @thesecretmaster "Required to tell the compiler…" has never been a good reason. And I can only hope that the syntax choice does not affect the optimiser at all. The point is that they are easier to understand for the programmer(s) that have to read them. The iteration variable is (supposed to be) initialised and modified only in that single line, which makes for a an easily recognisable pattern and correct code where the update step is not forgotten. It also works with continue; $\endgroup$ – Bergi Jul 14 '17 at 1:45
  • 6
    $\begingroup$ @Bergi Keeping the loop control variable in the for scope is a practical advantage as well. The more correct "expansion" of a for loop would be: {int i = 0; while(i < 100){++i;}}. $\endgroup$ – IllusiveBrian Jul 14 '17 at 13:51
  • $\begingroup$ In C# and C the declaration of the iteration variable ('i' in this case) can be inside or outside the opening parenthesis of the for statement, which means the scope can be only within the loop, or outside as well. This is a feature, not a bug: if you need the scope to extend beyond the loop, you can, and if you don't you don't need it to. I introduce while loops first, then explain for as an easier to read syntax. $\endgroup$ – user737 Jul 14 '17 at 16:18
25
$\begingroup$

I teach for loops using the following pseudocode, which we then translate into actual code:

for (initialization; condition; update)
{
    statement;
}

This logic helps justify why the order goes 1, 2, 4, 3 to start. I explain it as follows: a variable can only be initialized once so it happens first and never again. After that the variable is tested against the condition. If the condition is true, then the code proceeds to the statement. Only then will it reach update. If the first conditional test fails, the update code is never executed.

I have found that for loops are one of the few constructs that benefit most directly from drawing a flowchart. Using the visual diagram will help this logic become clearer.

Another benefit of teaching the for loop this way is that it becomes trivial to translate it into while or do-while with the same pseudocode constructs.

// while loop
initialization;
while (condition)
{
    statement;
    update;
}

// do-while loop
initialization;
do
{
    statement;
    update;
} (condition);

Once I do this, I can focus on each of these four parts, so students understand the different components that make up a loop regardless of the structure thereof.

$\endgroup$
  • 1
    $\begingroup$ That's a clear way to explain loops. I use a similar approach. $\endgroup$ – Edwin Torres D.Eng. Jul 14 '17 at 15:31
  • $\begingroup$ The do while and while loops stated aren't exactly equivalent. If after initialization, condition is false, then you never execute statement+update in the while loop, but in the do-while loop, you always execute statement+update at least once. This could easily lead to confusion. $\endgroup$ – Batman Jul 15 '17 at 21:58
  • $\begingroup$ @Batman Absolutely. That would be part of a broader instruction on the differences between and among these three loop constructs. Nevertheless, the general pseudocode holds true. $\endgroup$ – Peter Jul 15 '17 at 22:05
  • 1
    $\begingroup$ This is pretty much how I do it but I'm a little more explicit in each section: pre-loop-initialisations, condition-for-doing-iteration, post-iteration-actions and iteration-actions. Once they understand that the iteration is a single execution of the body and the loop is the entire thing, this seems to be easy to pick up (even for teens). $\endgroup$ – user2049 Jul 17 '17 at 1:49
9
$\begingroup$

It's about setting boundary conditions first. Provide them with a more concrete example, like filling a glass of milk. You kind of need a glass or container (let me know the next time you pour milk right on the counter) and that glass is some amount full. We'll just treat it as empty. Then, you stop filling the glass when it's full (or half full or whatever), unless you want milk everywhere.

Within those boundary conditions, you pour the milk jug. That is, mentally, you're thinking (in not so many terms):

repeat following until checks make you stop pouring milk
check: fullness of glass
check: how close the fullness is to the wanted end fullness
add some milk

That's exactly how a for loop thinks. It needs your conditions first, and then what you want to repeat while those conditions are true. (Yes, while - just like a while loop. I personally agree with thesecretmaster in that it makes a lot of sense to introduce while loops before for loops.) As you're pouring, you're constantly rechecking the level of the milk - "updating" a variable in your head as you act. So, if you were asking a computer to fill a glass of milk:

import milk
for (float glassFullness = 0; glassFullness < 1; glassFullness+=0.1) {
    milk.Fill()
}

That could, of course, also be written

import milk
float glassFullness = 0;
while (glassFullness < 1) {
    milk.Fill()
    glassFullness+=0.1
}

They're doing the same things; the for loop is just a condensed version.

Tl;dr: a for loop looks for the boundary conditions, and then does what it needs to do, rechecking with a redefined variable accounting for the change that has been made. You input what change needs to be made to the variable to account for the action.

$\endgroup$
  • $\begingroup$ Boundary conditions are very helpful in explaining loops. $\endgroup$ – user737 Jul 14 '17 at 16:21
8
$\begingroup$

tl;dr- A for loop is just a while loop that has extra slots for a pre-loop statement and a post-loop-run statement. Since they're pretty much the same thing just written differently, a for loop is best understood as syntactic sugar that's useful when it increases the readability of the code.

Sometimes new programmers are worried about performance, thinking that syntactic sugar might be less efficient. That's not the case; the compiler really doesn't care how you write these loops, it's all the same to it. So, students should be told to just write whichever's cleaner in the current context.

Homework/test question idea: Have students convert a for loop into an equivalent while loop, or a while loop into an equivalent for loop.

Mapping between for and while

The for loop:

for (prestatement; condition; poststatement)
{
    //  loop body
}

is equivalent to the while loop:

{
    prestatement;
    while (condition)
    {
        //  loop body
        poststatement;
    }
}

, where:

  • prestatement is an executable statement that happens only once, before the loop starts.

    • If omitted, then the loop just doesn't do anything extra before starting.
  • condition is a Boolean expression that's evaluated at the start of each loop execution, breaking if false.

    • If omitted, then there's no break check there.
  • poststatement is an executable statement that happens after the end of each loop execution.

    • If omitted, then the loop just doesn't do anything extra after each execution.

Note on scope

Even though

int i = 0;
int i = 0;

is illegal code because we can't declare i twice in the same scope,

{
    int i = 0;
}
{
    int i = 0;
}

is legal because both i's are in different scopes.

This scoping issue is why we have the while statement above wrapped in extra {}'s, since the prestatement belongs in that scope.

for loops can omit parts

Might seem weird at first glance, but

for (;;) { }

is actually perfectly legal code! Since it omits a break condition and doesn't do anything, it's basically an infinite loop that runs forever without doing anything.

In C#, it doesn't consume CPU time, but it also blocks the thread from progressing. For example,

for (;;) { }    //  Infinite loop
foo();          //  Never happens because the above loop never ends
$\endgroup$
  • $\begingroup$ Not only syntactical sugar around a while loop, as the continue statement of the while loop skips the "poststatement". $\endgroup$ – Michel Billaud Jul 19 '17 at 19:48
7
$\begingroup$

Theoretically, I teach the while loop first. Others have already explained how and why, but I'll go ahead with an example:

void showRating( int rating )
{
   int i = 0;
   while ( i < rating )
   {
      putchar( '*' );
      ++i; 
   }
   putchar( '\n' );
}

Some of the examples will be better expressed as for loops, and that's where I introduce the for loop, as a way of containing the essential elements in a concise construct. I might ask what happens if I forget to increment i:

void showRating( int rating )
{
   int i = 0;
   while ( i < rating )
   {
      putchar( '*' );
   }
   putchar( '\n' );
}

And we compile it and run it.

Then I show how the for loop might help us not forget the counter:

void showRating( int rating )
{
   int i;
   for ( i = 0; i < rating; ++i )
   {
      putchar( '*' );
   }
   putchar( '\n' );
}

Too often, people think first of counting when they think of loops, and that's why they jump to the for loop first. But the C for loop is not really the counted loop that BASIC has led us to believe it is. And the English grammar for the word is not limited to counting, either.

So I'll throw some for loops in that don't strictly count.

I also teach the pun on forever that used to be the standard flag for an indeterminate loop:

for( ;; )

I'm likely to teach the post-test do ... while loop after having shown an indeterminate loop with a break at the end:

for ( ;; )
{
   /* Always executes once. */
   if ( condition ) break;
}

Now I would want to use something concrete here so I can show that it really always executes once, but I don't have time to make something up today.

Then I can show them the do .. while loop:

do
{
    /* Always executes once. */
} while ( !condition );

Somewhere along in here I'll introduce the faux style thing about bracket placement and warn them not to argue with the managers about where to place them.

$\endgroup$
  • $\begingroup$ Great set of variations. And, it is not good to argue with Visual Studio about how to format the code either! $\endgroup$ – user737 Jul 14 '17 at 16:26
7
$\begingroup$

I think the way to teach it is not by talking about order of operation, but by teaching the overall concept of a looping. The for header contains the steps that are generally common to looping: initialize iteration variables, test whether the loop should proceed, and update variables between iterations.

As others have suggested, you could start by teaching while loops, since they have simpler structure. Then you could point out that many of these loops have some common structure: they initialize a variable before starting, and update that variable at the end of the body. This is so common that we have a special for statement that encapsulates it.

This is also useful as an illustration of modularity, which is a cornerstone of good programming. Whenever you find yourself doing similar things over and over, give that thing a name and encode it as a function, macro, structure/class, etc. In this case, it's just happening at a higher level, in language design: much of the evolution of languages over the decades has been finding common programming patterns and creating languages that have them as built-in operations and/or functions (e.g. foreach in PHP).

$\endgroup$
6
$\begingroup$

I used to have a role where I supported college students in CS1. I developed the following exercises to address this exact issue:


Counting code executions

Peer Instruction exercise about for loops

Text version:

How many times do each of the following pieces of code execute?

int sum = 0;
int i;
for(i = 1 /*A*/; i < 5 /*B*/; i = i + 1 /*C*/)
{
  sum = sum + i; /*D*/
}

Hint: translate a while loop -- the pieces should execute the same number of times as the do here.

Translating loops

Work on each of these individually first, then partner up. Switch partners between exercises.

A. Convert this while loop into a for loop:

int k = 0;
while (k < 7) {
  cout << "Hola " << k << end1;
  k = k + 2;
}

What is the output of the loop?

What is the value of k after the loop completes?

Watch your peer leader be a human compiler on this loop.

B. Convert this for loop into a while loop

for (int countdown = 10; countdown >= 0; countdown--) {
  cout << "T-minus " << countdown << end1;
}
cout << "BLASTOFF!!!" << end1;

What is the value of the countdown after the loop completes?


If you teach very well maybe the questions are too easy, but in my setting they led to a lot of improvement. Notice that I included a counter increasing by something other than 1, and a loop counting downwards, since my students often didn't see examples like that in class.

$\endgroup$
  • $\begingroup$ Hyper-clarity is teaching well. This is an excellent exercise. Welcome to Computer Science Educators. I hope we hear more from you in the future! $\endgroup$ – Ben I. Jul 16 '17 at 22:14
  • 1
    $\begingroup$ This isn't just an answer; it also has useful material in it that'd be of general value to educators. At current, SE.CSEducators doesn't really have a good mechanism for promoting this content, though there might be an argument for cloning StackOverflow's recent Documentation feature here - just, instead of being documentation on programming, it'd be a repository of teaching material. $\endgroup$ – Nat Jul 16 '17 at 22:53
  • 1
    $\begingroup$ @Nat That actually sounds like a fantastic idea! If you want to, create a meta question requesting it. It's certainly unprecedented, but it's worth asking :) $\endgroup$ – thesecretmaster Jul 16 '17 at 22:59
  • $\begingroup$ @Nat (and nova) I forgot to mention, come by the chat room. We'd love to meet you! $\endgroup$ – Ben I. Jul 16 '17 at 23:00
4
$\begingroup$

I am not an educator, but I hope my answer will be helpful.

In B.A.S.I.C, which I learned as a child, there are "for-to-step" loops and their argument order is the same as C style languages. I have to remember them like this in order to keep it right for myself.

This is QBASIC

FOR counter = start TO end [STEP]
    [Statement Block] 
NEXT counter

or

FOR i= 100 TO 0 STEP -1
    PRINT i 
NEXT i

This helps me personaly understand what's happening in those loops.

Another thing you might try is an alteration of what you posted above:

 for (1; //initialize
      2; /* are we done? */ 4 /*increment*/){
      3 //do the work
 } // finish
$\endgroup$
4
$\begingroup$

Maybe the

for (int i=0; i<10; i++) 
{ ... }

loops seems funny when you compare it to the "interval" loop of other languages, like Pascal's

for I:=1 to 10 by 1 do 

but the C for loop was designed as a much more general linguistic mechanism than loops with counters.

The objective was to keep all elements of the "control" of the loop together on one side for better maintenance, and the body of the loop on the other side.

So we have a lot of common idioms, like

 for (char *ptr = string; ptr != '\O'; ptr ++) {....}

 for (struct Cell p = first; p != NULL;  p = p->next) {  .... }

The loop over an interval is only an instance. Maybe the problem is in the overuse of counters in the exercices for beginners (even when the range-based for loop is available). A survival from good old Fortran programming :-) ?

$\endgroup$
3
$\begingroup$

However, don't neglect the fact that the first and last of the three sections between the parentheses can have (in C, anyway) multiple statements separated by commas (comma is an operator in C).

int i, j;
for(i = 0, j = 100; i < 100; i++, j = 2 * j + i){  
    //do something strange  
};  

You can write quite a lot of a program in a single for statement. The final section needn't just increment or decrement things. Arbitrary complexity is possible. Probably not a good idea to encourage it, however. But students reading code of others may come across it, so should be aware.

One use of this is to iterate over a two (or more) dimensional array in a single for statement.

Because of the complexity, it is probably best to show how it maps to a while statement as suggested in another answer here

$\endgroup$
  • $\begingroup$ Is the order of evaluation in your "step" even defined? $\endgroup$ – Džuris Jul 14 '17 at 3:14
  • 1
    $\begingroup$ @Džuris yes. There is a sequence point between the evaluation of the left and right hand sides of the comma operator. $\endgroup$ – hobbs Jul 14 '17 at 5:06
  • $\begingroup$ (but calling it "multiple statements" isn't accurate. It's a single expression. And the int i = 0, int j = 100 part actually isn't valid because that's not how declarations work, though it would be without the second int). $\endgroup$ – hobbs Jul 14 '17 at 5:08
  • $\begingroup$ @hobbs oops and Thanks. Corrected to remove the declaration from the loop. $\endgroup$ – Buffy Jul 14 '17 at 9:08
  • $\begingroup$ Yay for the comma operator! I have used it to achieve some significant and very necessary functionality in the past, which I could not do otherwise (trying to fit a macro in place of a function, what is called 'inlining' these days). $\endgroup$ – user737 Jul 14 '17 at 16:23
3
$\begingroup$

I love thesecretmaster's answer of showing the mapping between the two loops (and I will almost certainly do that in the future). However, as an AP Computer Science A teacher, I teach in Java, and there is a subtle trap in saying that

for (int i = 0; i < 100; i++){
     // do something
}

is equal to

int i = 0;
while (i< 100) {
  // do something
  i++;
}

The two loops are identical, but of course there is one subtle but important difference: the scope of i (and its corresponding pollution of the namespace). Java considers the int declared in the first section of the for-loop header to be declared in the loop, and therefore out of scope once the loop ends.

The while loop is really equivalent to this loop in C:

int i;
for (i = 0; i < 100; i++){
     // do something
}

If you are to take the path of mapping equivalencies between the loops, you really should also point out this trap.

$\endgroup$
  • 1
    $\begingroup$ You have a good point about the scope issue, though that's less a difference and more an issue of an incomplete translation. As noted in @IllusiveBrian's comment, {} need to be included to control for scope - which may look unusual since most people don't care enough about scope, but it's needed to maintain equivalence. $\endgroup$ – Nat Jul 14 '17 at 15:08
  • 1
    $\begingroup$ For example, the int i = 0; while (i< 100) { /* do something */ i++; } above should be { int i = 0; while (i< 100) { /* do something */ i++; } }. $\endgroup$ – Nat Jul 14 '17 at 15:11
  • 1
    $\begingroup$ TIL about the purpose of extra code braces. Thanks! $\endgroup$ – Ben I. Jul 14 '17 at 15:12
  • $\begingroup$ Sometimes you want to be able to see the final state of the loop control variable outside the body of the loop. I teach that also. "Students do the darndest things." $\endgroup$ – user737 Jul 14 '17 at 16:28
  • $\begingroup$ scope and pollution can be limited by a pair of curly brackets around declaration + loop. $\endgroup$ – Michel Billaud Jul 19 '17 at 20:14
2
$\begingroup$

Another way of looking at this is to explore what is missing or lost at various stages:

In Java

For each: for( String s : < some iteratable collection > ) foo(); loses the index of the loop. It never has one unless you add it.

for (int i=0; i< n; i++) foo(); loses the index after the loop when completed,

while other loops preserve it.

Many of the posts offer good advice for explaining the behaviour of the for loop. Translations to and from while loops are traditionally used. Strictly speaking a for loop is never required, it's a convenience, as is the for each loop.

The more tricky uses of a for loop should be very carefully considered before teaching -- high risk low reward.

$\endgroup$
  • $\begingroup$ I agree about de-stressing low reward features. However, they can't be neglected entirely as students (and professionals) still need to read the code of others who might not exhibit such "sanitary" practices. $\endgroup$ – Buffy Feb 6 '18 at 15:17
  • $\begingroup$ I would add that it is also heavily tested on the AP CS A test, where they love to introduce off-by-one errors and tricky ending conditions. $\endgroup$ – Ben I. Feb 6 '18 at 16:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.