5
$\begingroup$

Edit: It's been quite a while since I posted this question, and in the intervening time, I have come to love the foreach loop. However, in order to avoid invalidating answers, I leave the text unchanged below:

I personally dislike the foreach loop, so I tend to avoid them when I program. However, in spite of this preference, I still end up teaching about them as part of the AP Computer Science A curriculum.

 for (Type item : iterableCollection) {
      // Do something
 }

I am particularly interested in experiences from those who have actually taught this in a classroom (though, as always, pedagogic thoughts from others are also welcome). What do you do to teach about the limitations, costs, and advantages of this loop structure?

I will put my own answer below alongside the others.

$\endgroup$
6
  • 4
    $\begingroup$ You learned in the procedural era, didn't you? $\endgroup$ Commented Jul 15, 2017 at 4:37
  • 1
    $\begingroup$ I'd just like to say that for the past couple years I've been using languages that don't have a c-style for loop. I never missed it. $\endgroup$
    – CAD97
    Commented Jul 15, 2017 at 20:16
  • 9
    $\begingroup$ Can you share your reasons why you dislike it? $\endgroup$
    – Bergi
    Commented Jul 16, 2017 at 7:07
  • 5
    $\begingroup$ Yikes, I hope you're not suggesting you wouldn't teach students about the foreach loop if you weren't forced to by your curriculum! $\endgroup$ Commented Jul 18, 2017 at 16:11
  • 1
    $\begingroup$ @ivanian java has many constructs that you cannot iterate through with an index counter, including Maps, which are functionally the same as arrays indexed by objects. The foreach construct is very convenient for avoiding hard to track but simple words in looping. $\endgroup$
    – pojo-guy
    Commented Feb 4, 2018 at 4:38

12 Answers 12

28
$\begingroup$

I think the advantages are huge, overwhelming.

They avoid

  • off by one errors - the bane of the novice programmer
  • needing to allocate an explicit iterator - avoiding adding names to the namespace.

They enable

  • thinking about iteration at a higher level than just counting
  • compact coding

I use them whenever available. The collection knows its members. Just ask it to yield them up. It is a brilliant design.

Programs in object-oriented languages like java should not have to all look like C programs, nor should the programmer have to always dip to the level of C to use them. C is a better assembler (for the pop-11). But few programs need that mind-set.

My preference is to teach concepts from the top-down. Higher level thinking means (in part) "closer to the language of the problem". Then, when necessary go to a lower level - closer to the machine or closer to the level of the solution. In practice you seldom need to go down the scale very far for students to get it. And you never need to recapitulate the entire history of computing to have students understand how to work in modern languages, whatever the paradigm.


I actually try to move even further up the abstraction scale as illustrated in this post https://cseducators.stackexchange.com/a/1379/1293

I include this in a library so that students learning other things can do simple iterations even before loops are discussed. It is quite useful to be able to do something a fixed number of times without formally counting it. However, it is only especially useful if you want to delay for and while so that you can teach, say, object-oriented abstraction principles.

$\endgroup$
1
  • 6
    $\begingroup$ Speaking as a Java programmer, not a teacher, I only use the old style loops when there is a clear benefit. The foreach loop can compile more efficiently, and as Buffy says they prevent common errors that beginners are prone to make, but also plague more experienced programmers who get a little lax. $\endgroup$
    – pojo-guy
    Commented Jul 15, 2017 at 3:57
10
$\begingroup$

I think foreach loops are great, and are almost always preferred over for loops. While for loops are fundamental material and definitely should be covered, they're sort of infrequently used in practice -- when iterating, we almost always iterate over lists and other collective (and it's generally considered bad practice to modify a data structure you're iterating over anyways, so it's no big deal if we lose that -- see the recent trend towards functional programming and immutable data structures).

(After all, there's a reason why languages like Python basically got rid of for loops in favor of foreach loops! Generally, when you're looping, it's because you're looping over lists, sets, collections, or infinite generators.)

That said, it would probably be a bad idea to introduce foreach loops immediately. In the courses I help teach, we wait until we've introduced the following concepts first:

  1. Using regular for loops
  2. OOP
  3. Using basic data structures (ArrayList, LinkedList, Stack, Queue, etc)

Once we hit that point, we typically do the following:

  1. Start by talking about sets and maps (in a previous lesson).
  2. Intentionally run into a limitation: if hash sets are inherently unordered, and the concept of getting the "i-th" element of a hashset is simply undefined, how do we iterate over it? This serves as a great motivator for why we need a new looping concept (if you introduce foreach loops earlier, students are likely to brush this off by treating as only a mildly more convenient form of a for loop, which is missing the point).
  3. Rather then jumping into foreach loops, we detour by talking about iterators. Time permitting, we implement a basic one for an arraylist we previously wrote together in class -- the goal is to demystify each step.

    We have students use Scanners to do file IO/string parsing in previous assignments, so they usually get the hang of using iterators pretty quickly/find they have a good intuition for using iterators.

    If we're feeling really ambitious, we can discuss this lets us implement data structures that are infinitely long.

  4. But of course, iterators are pretty clunky, so that serves as good motivation for the final part of the lesson: foreach loops. We introduce the notion of "syntactic sugar", and demonstrate how Java internally rewrites the foreach loop into a while loop using an iterator. (This helps demystify a lot of the "magic" of foreach loops for students).

  5. We finish by talking about some of the limitations of foreach loops (e.g. you can't modify the collection you're iterating over) and discuss stylistically when you should use for loops vs iterators vs foreach loops.

    If you have time, this would also perhaps be a good place to discuss how it can be useful to intentionally limit what you can do in order to gain something in exchange. In this case, we exchange the ability to modify our data structure in exchange for cleaner and more readable code and the ability to iterate over a wider variety of data structures. This actually echos some of our earlier discussions on hashmaps/hashsets -- by removing "indexability", we were able to get faster lookups.

All of this usually takes about one hour-long lesson, assuming you've covered basic usage of maps and sets in a previous lesson.

Finally, to help reinforce the point, we start mandating that students use foreach loops whenever possible on their homework, and start deducting points if they use for loops in places where it's not the best option.

The very next homework assignment requires students to manipulate and iterate over sets and maps, so in practice it's almost impossible for students to lose points for this.

$\endgroup$
9
$\begingroup$

There is much discussion here about how a traditional for seems more powerful than a foreach, but a traditional for with sentinel value can only be used to iterate over an indexable collection. Data structure like sets and maps are prevalent, and iterating over them by index would be technically and conceptually absurd. (You've already outlined places where for is invaluable, but it's applicability is technically limited to only a few (granted, extremely common) types).

Furthermore, good code expresses intent, which is why we have types like 'bag' which may seem just like an inferior list/vector: if you have data which is conceptually unordered, then you are imposing a layer of complexity on the mapping between code and reality by using a outwardly ordered data structure (e.g. ArrayList)...

... foreach is about enumerating/iterating over collections, and expresses the intent much more clearly and succinctly than a traditional for loop, which is just a familiar and tidy way of expressing an equivalent while loop. foreach lets you exploit the ICollection, or Iterable, or IEnumeable abstraction, which is fundamental to reusable collection handling code (I'm a C#er, not sure what the Java interfaces are called).

Others have already detailed why foreach is great for maintainability (aside from expressing the intent of the code better, it's so much harder to get wrong, and even easy to get right), and I would agree/suggest that you should typically use a foreach whenever you can quickly work out how to do so. For example, if you need to keep track of the previous value in the loop (see Buffy's answer), this is readable and easy enough to write in a foreach loop that the benefits of having a more general purpose piece of code (foreach will work on any collection) will be of sufficient value to eclipse any perceived greater clarity in the for loop.

I'll just add that even if you are throwing together code you don't expect other people to see, or to even look at yourself in the future, writing at a high level of abstraction helps to prevent errors, and gives your type system the best chance of catching mistakes for you. If you are just iterating over the values in a collection (which happens a lot) then use a foreach loop because it is less powerful: you've told the code that you don't want to modify the collection, now when you try to, your language can scream and shout at you (that's its job!).

Just for reference, I'm a C#er who has done a lot of (C#) code-golf. I love traditional for-loops, and can surely read and write them quicker than most: both loops are applicable in many places in the real world; foreach usually wins out where they overlap.

$\endgroup$
2
  • $\begingroup$ Agreed, I just posted a comment myself about the ability of iterators to represent unindexable data and agree it's a really important point - iterators are a simpler interface that requires less of your data source, meaning they generally scale better. Likewise, the things iterators can't do are mostly language limits of Java (no tuples, no returning references to items in a list) rather than strict limitations of the concept themselves. $\endgroup$ Commented Jul 15, 2017 at 10:55
  • $\begingroup$ I note that I am especially careful when I write code only for myself. Indentation, brace all, intention revealing names, ... All of it. I know that if it is just for me, I'll return to it later and don't want to have to de-obfuscate my own code. And I especially don't want to have to add braces to code that omitted it on an update. Get it right. Get it done. $\endgroup$
    – Buffy
    Commented Jul 15, 2017 at 11:08
4
$\begingroup$

To do something like the following with foreach...

// Exercise 3:
for (int j = 1; j < myDoubles.size(); j++){
   if (myDoubles.get(j) < myDoubles.get(j-1))
        return false;
return true;

consider the following, expressed as a static method

public static boolean isSorted(ArrayList<Integer> values){
    boolean first = true;
    Integer previous = Integer.MIN_VALUE;
    for(Integer value: values){
        if(first){
            first = false;
        } else {
            if (value < previous){
                return false;
            } 
        }
        previous = value;               
    }
    return true;
}

I won't try to argue that this is simpler, however it does illustrate some important techniques useful elsewhere. The processing of the first element is handled explicitly, often important to do. In the original for loop version it is handled by starting at 1, which is easy to miss. The other technique is the handing off of a current value to a previous reference. There are a couple of other simpler things here, such as MIN_VALUE (which isn't used, but avoids an uninitialized value - that will fail the < test also). A slight modification would work for Doubles, of course.

$\endgroup$
3
  • $\begingroup$ I stand by the pedagogic usefulness of my example, though I will add yours into my future discussions of the tradeoffs. If you add an extra int, then there are no limits on power, because you could also just add back in the missing sentinel value. But the complexity of the code rises, and the fact that you're not given a sentinel in the first place is still a limit of the form itself that you must work around. And without the additional variable, the sequential comparison is not possible. $\endgroup$
    – Ben I.
    Commented Jul 14, 2017 at 23:26
  • 1
    $\begingroup$ @BenI: a third alternative you could bring up is implementing a helper function that transforms an iterable into an iterable of pairs and foreach over that. The syntax is still a bit clunky because Java had no built-in syntax for manipulating/destructuring types, but it would make the foreach approach clean again/visually similar to the for loop solution. It's perhaps cheating in the context of your exercise since we're not sticking with just foreach loops, but this would be a good opportunity to help students start thinking about iteration on a higher level. $\endgroup$ Commented Jul 14, 2017 at 23:59
  • $\begingroup$ See my comment on @BenI.'s answer: Using java.util.Optional makes this much simpler, because you don't need to have a special case to handle the first iteration (the different behaviour of Optional.empty() vs Optional.as(value) handles it for you). And this is, perhaps, an important point here: working in the foreach style requires a slightly different approach, and the tools to make that approach convenient have taken a while to get here (at least in Java... Optional types have been around for years in functional languages), but now they are it is critical to learn how to use them. $\endgroup$
    – Jules
    Commented Jul 15, 2017 at 17:10
3
$\begingroup$

Part 1

When I introduce the foreach loop, I do it the context of a lecture on ArrayLists and wrapper classes. I first just kinda slip it in unannounced:

double total = 0.0;
for (double currentValue : myDoubles)
    total += currentValue;
System.out.println(total);

Then we turn to it and begin to break it apart a little. I first compare it to

for (int j = 0; j < myDoubles.size(); j++){
    double currentValue = myDoubles.get(j);

. But then I tell the students that there is a hidden limitation. I want to see if they can spot it, and I provide them with three re-writing exercises (that are also practice exercises on the prior concepts) to help them find it:

// Exercise 1:
double total = 0.0;
for (int j = 0; j < myDoubles.size(); j++){
    total += myDoubles.get(j);

// Exercise 2:
for (double currentValue : myDoubles)
   total += currentValue;

// Exercise 3:
for (int j = 1; j < myDoubles.size(); j++){
   if (myDoubles.get(j) < myDoubles(get(j-1))
        return false;
return true;

When the last one turns out to be impossible, we discuss the limits imposed by not having a sentinel value.

Part 2

We revisit foreach loops later in the year when we deal with multi-dimensional arrays. Towards the end of that lesson, they are given this task:

For some int[][] ma, fill every element with the value 3.

We discuss the standard for loop solution:

for (int y = 0; y < ma.length; y++)
    for (int x = 0; x < ma[y].length; x++)
        ma[y][x] = 3;

And then I bring them back to foreach with this pretty-looking thing:

for(int[] outer : ma)
    for(int x : outer)
        x = 3;

... which, of course, doesn't work at all. I ask the kids to pair up and try to figure out why before we all come back together to have a discussion.

This example brings us right to the heart of copy-by-value (and provides another chance for us to revisit references). I consider the discussion over only when the students seem to understand why the nested foreach doesn't work, but this loop does:

for(int[] outer : ma)
    for(int x=0, x<outer.length; x++)
        outer[x] = 3;
$\endgroup$
5
  • $\begingroup$ I think if you are going to talk about the limits, an interesting point to make is that Java's limited support for things like tuples make it harder to implement good solutions inside iterators. Python, for example, makes part 1 rather easy (any(first < second) for (first, second) in zip(items[1:], items))). Likewise, C#'s ref returns could be used to solve the second case (although they aren't, as far as I know). I think it's valuable to note that the iterator provides a lot of value but in some cases is too limited (by it's design in that language) to solve some problems. $\endgroup$ Commented Jul 15, 2017 at 10:45
  • 2
    $\begingroup$ Most importantly, it's increasingly common for programmers to have to deal with large amounts of data, and storing all of your data in memory isn't always possible. Traditional indexing for loops are generally useless when dealing with lazy data, and so understanding how to do things with iterators rather than indexing means your code can scale far more effectively. $\endgroup$ Commented Jul 15, 2017 at 10:50
  • $\begingroup$ "When the last one turns out to be impossible"... Optional<Double> last = Optional.empty(); for (Double d2 : myDoubles) { if (last.map(d1 -> d2 < d1).orElse(false)) return false; last = Optional.of(d2); }. Ok, it's a little harder to understand at first grasp than yours, but has the advantage that it's O(n) for all standard collection classes, whereas your version is O(n^2) for linked lists, and is also thread safe if myDoubles is a concurrent collection. And it promotes a way of thinking about the problem that is more generally useful and less fragile than working directly with indices. $\endgroup$
    – Jules
    Commented Jul 15, 2017 at 16:55
  • $\begingroup$ (and yes, I do think these concepts are fundamental enough that they should be taught early enough that students looking at the foreach loop should already know about them: the "optional" container type, lambda functions, "map" functions on containers -- we think of them as advanced topics because they were introduced late to most of our popular languages, but the different way of thinking about problems they enable can make for much better code in many situations, and they should therefore probably be our first choice for tackling most problems, so why not teach them first?) $\endgroup$
    – Jules
    Commented Jul 15, 2017 at 17:35
  • $\begingroup$ Oh. When I saw you talking about the limitations in the question the first one which came to mind is that you can't call remove() on the Iterator, but that doesn't seem to enter into your considerations at all. I see for (Foo foo : foos) { as sugar for for (Iterator<Foo> it = foos.iterator(); it.hasNext(); ) { Foo foo = it.next();, but all of the answers seem to be about iterating over arrays rather than collections. $\endgroup$ Commented Jul 17, 2017 at 15:33
3
$\begingroup$

I don't think you should teach the foreach loop and then point out all of its "flaws" when compared to a traditional for loop. You should treat them both as tools, and different tools are used for different tasks. Maybe teach them a bit of the history (traditional for came first, then foreach was added in Java 5) and show them an example of when it would be best to use each.

Also note that you seem to be skipping over a third looping mechanism, which the foreach loop is based on: the Iterator.

Compare two tasks, like printing out every value in an ArrayList and removing all elements that match a certain criteria (for example, values that are less than 0). Show them how you'd do (or how it's not possible to do) each task in all three types of loop.

With the simple printing task, the foreach loop is perfect. Using a traditional for loop or an Iterator is probably overkill for this task. But with the task of removing elements, the foreach loop will throw an exception! If you're using a traditional for loop, you have to loop backwards or be very careful about which index you're looking at (otherwise you might skip elements). And an Iterator is probably best suited for this task.

The point is that everything in programming should be treated as a tool. A tool isn't inherently better or worse than other tools, it's just better suited for specific tasks. Talk about those tasks.

$\endgroup$
3
$\begingroup$

Addressing the subquestion regarding limitations of foreach:

I've been a reader for the AP Computer Science A exam since 2004. The most common mistake we see in student solutions regarding foreach loops deals with two important limitations:

  • foreach loops lose any association between items in an ordered collection (e.g. array, ArrayList) and their indexes. If the core algorithm requires knowing the location of an item as well as its value (e.g. returning the index of an item in an ArrayList), foreach loops are usually inappropriate.

  • foreach loops cannot modify the base collection while iterating over it. If the core algorithm requires changing the base collection, the foreach loop is inappropriate.

$\endgroup$
2
  • $\begingroup$ Thank you for sharing your insight as an AP CS exam reader. I'll look for more of your answers. $\endgroup$ Commented Jul 23, 2017 at 3:13
  • $\begingroup$ Good points! I'd also be extremely interested in your insights on this question as well. $\endgroup$
    – Ben I.
    Commented Jul 23, 2017 at 4:14
2
$\begingroup$

You should definitely teach foreach loops. Why?

  1. Foreach loops are the main type of loop in Python.
  2. Python is the awesomest.
  3. Therefore foreach loops are important.

All "joking" aside, foreach loops are super useful - I use them all the time. They're the difference between

x = 0
while x < len(list):
    print(list[x])
    x+=1

and

for i in list:
    print(i)

This illustrates a couple nice things about foreach loops:

  1. Very short, clean code.
  2. Less variables to keep track of - nice for any programmer, let alone novices.
  3. Much easier to iterate through lists/arrays/dictionaries/strings or perform batch operations.
  4. Allows you to introduce list comprehensions much more easily (what's a list comprehension but a condensed foreach loop?), and everyone knows list comprehensions are the best thing since sliced lists.
  5. Very commonly used in awesome programming languages like Python. (Seriously, though, Python's pretty commonly used - best to be prepared for using it.)
$\endgroup$
3
  • 1
    $\begingroup$ It should be noted that Python isn't the only language where the use of iterated loops is much more common than any other type, which is perhaps an important point: you can learn to work with indices rather than iterators in some environments, but if you never learn to work effectively with iterators, you'll be stumped the moment you try to work in an environment where indexing isn't realistic (e.g. if you try to work in a functional langage, where linked lists are the de-facto standard collection and therefore indexing is too slow to be useful for most applications). $\endgroup$
    – Jules
    Commented Jul 15, 2017 at 17:08
  • 3
    $\begingroup$ i and x are the exact opposite of what I'd expect for element and index variabes. $\endgroup$ Commented Jul 15, 2017 at 18:13
  • 3
    $\begingroup$ Comparing a foreach to a while loop rather than to a regular for loop is not particularly a fair comparison. $\endgroup$ Commented Jul 15, 2017 at 18:29
1
$\begingroup$

Ruby would be a really interesting language for you to work with, given your dislike of foreach loop. It provides an good way to think about a for loop. In ruby, the for keyword does not exist. There's no way to actually create a for loop. But any collection (Enumerable) has a #each method, which iterates of the elements of the collection. For example:

words = ["apple", "car", "coffee", "document"]
words.each do |word|
  puts word
end
#=> apple
#=> car
#=> coffee
#=> document

To implement a traditional for loop, you need to iterate over a range (in ruby the a..b notation generates an Enumerable from a to b):

words = ["apple", "car", "coffee", "document"]
[0..3].each do |i|
  puts words[i]
end
#=> apple
#=> car
#=> coffee
#=> document

Which syntax makes more conceptual sense and is more readable? I'd certainly say the first one. It also will help students seperate the concept of a loop with the specific syntax for creating a loop.

$\endgroup$
1
$\begingroup$

Don't forget to mention what information is not available when they choose between certain looping constructs:

foreach loops lose position data (index, relative locations (i and i-1, etc) for(int i;;) loops lose the index variable (i) upon completion of the for loop

foreach loops are designed to work with collections & iterators.

See Teaching the humble for loop

Also from a AP CS A exam perspective, the trap is usually that you don't have an index value. The advice I give to the students taking this exam is to avoid the foreach when writing exams because in the majority of cases they will require the index.

$\endgroup$
2
  • $\begingroup$ I encourage you to fill out your answer. Short answers like this tend to get ignored, or worse. Help future readers draw the correct inferences that you see yourself. $\endgroup$
    – Buffy
    Commented Feb 7, 2018 at 14:11
  • $\begingroup$ Your students would need to be aware of the the issues around using iterators -- attempts to modify the underlying collection, copied elements, etc. $\endgroup$
    – Mr Bradley
    Commented Feb 7, 2018 at 15:07
1
$\begingroup$

There is no "limitation" or "tradeoff".

The point is, we express ideas at a given level of abstraction.

The "forall" is for ranging over the values in a collection.

The for(int i = 0; i < n; i++) etc is for ranging over numbers, that sometimes happen to be the valid indices in some array. Quite a different matter.

Use the appropriate level of abstraction. The for loop is not a limitation over the while loop, or goto+if constructs.

Edit: actually some colleagues don't want to teach the foreach, because it comes with a break when you want to leave it before its natural end, which happens rather frequently.

And, you know, break is somehow like goto, which is considered awful and definitely forbidden. Burn these heretics and their for each loop!

$\endgroup$
0
$\begingroup$

I like this loop. A lot. You don't go out of bounds. It eliminates an important source of errors. I encourage its use when iterating through collections. I also ask students who make collections to implement Iterable so that they can use the collections for loop on their custom collection.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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