Skip to content
VLSI Mentor

Perl Part I · Module 9 · Loops

foreach and the C-Style for

Perl has two loop shapes that both start with the word for, and they are not variations on each other — they answer different questions. foreach walks a list and hands you each item, which is what you want in the large majority of loops you will write. The three-part C-style for counts, and you reach for it when the position matters as much as the value, or when the step is not one. The practical skill is knowing which question you are asking, because the counting form brings an arithmetic bound along with it, and the most common way to get that bound wrong produces one extra iteration that reads a value that was never there and says nothing about it.

Foundation15 min readPerlPart IControl FlowLoops

Part I · Module 9 · Chapter 9.1 · foreach and the C-Style for

1. Three Ways to Walk an Array

Azvya Education Pvt. Ltd.VLSI Mentor
three-forms.pl
use strict;
use warnings;

my @blocks = ("alu", "fifo", "regf");

print "foreach     :";
for my $b (@blocks) { print " $b" }
print "\n";

print "for 0..\$#    :";
for my $i (0 .. $#blocks) { print " $i=$blocks[$i]" }
print "\n";

print "C-style for :";
for (my $i = 0; $i < @blocks; $i++) { print " $i=$blocks[$i]" }
print "\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
foreach     : alu fifo regf
for 0..$#    : 0=alu 1=fifo 2=regf
C-style for : 0=alu 1=fifo 2=regf

All three reach the same three elements. The difference is what each one gives you and what it costs to read.

FormGives youReach for it when
for my $b (@blocks)the itemalmost always
for my $i (0 .. $#blocks)the indexyou need the position, or two arrays in step
for (my $i=0; $i<@blocks; $i++)the index, with explicit controlthe step is not 1, or the condition is not a simple bound

foreach is the default and for is the exception. That ordering matters: a loop written with an index when it did not need one has an extra variable, an arithmetic bound, and a subscript on every line, all of which can be wrong. A foreach loop has none of those.

for and foreach are the same keyword in Perl — entirely interchangeable in every form. The convention most code follows is foreach for the list form and for for the counting form, purely as a signal to the reader.

Note $i < @blocks in the third line: @blocks in that numeric comparison is its element count, which is Chapter 5.7's rule. That is why the C-style form needs no scalar().

2. The Bound That Is Wrong by One

Azvya Education Pvt. Ltd.VLSI Mentor
offbyone.pl
use strict;
use warnings;

my @blocks = ("alu", "fifo", "regf");

print "0 .. \$#blocks      : ";
for my $i (0 .. $#blocks) {
    print defined $blocks[$i] ? "$blocks[$i] " : "UNDEF ";
}
print "\n";

print "0 .. scalar(\@blocks): ";
for my $i (0 .. scalar(@blocks)) {
    print defined $blocks[$i] ? "$blocks[$i] " : "UNDEF ";
}
print "\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
0 .. $#blocks      : alu fifo regf 
0 .. scalar(@blocks): alu fifo regf UNDEF
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet

One extra iteration, reading an element that does not exist, and nothing on STDERR.

$#blocks is the last index — 2 here. scalar(@blocks) is the count — 3. A three-element array has indices 0, 1 and 2, so 0 .. 3 visits a fourth position. Chapter 5.2 established that reading past the end of an array is silent and does not extend it, which is exactly why this costs you nothing visible until the body does something with the value.

The only reason this printed UNDEF rather than a blank is that the body tested defined first. Without that test, print "$blocks[$i] " would have produced a warning — but one warning in a run that has others, for a loop that completed normally and exited 0.

WantCorrect bound
every index0 .. $#array
every index, counting formmy $i = 0; $i < @array; $i++
the count itselfscalar(@array)

$#array and scalar(@array) differ by exactly one, always. The habit is to use $#array in a range and < @array in a C-style condition — both already correct, so there is no arithmetic to get wrong.

3. When the Counting Form Is Right

Three cases where an index genuinely earns its place.

Azvya Education Pvt. Ltd.VLSI Mentor
reverse-step.pl
use strict;
use warnings;

my @b = ("a", "b", "c", "d");

print "reverse   :";
print " $_" for reverse @b;
print "\n";

print "every 2nd :";
for (my $i = 0; $i < @b; $i += 2) { print " $b[$i]" }
print "\n";

print "by index, descending:";
for my $i (reverse 0 .. $#b) { print " $i=$b[$i]" }
print "\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
reverse   : d c b a
every 2nd : a c
by index, descending: 3=d 2=c 1=b 0=a
  • Backwards over the values needs no index at all — reverse @b is a list, and foreach walks it. The counting form is unnecessary here.
  • A step other than one does need the C-style form: $i += 2.
  • Backwards with the index is reverse 0 .. $#b, which is clearer than a decrementing C-style loop and cannot get its bound wrong.

Two more legitimate cases:

Azvya Education Pvt. Ltd.VLSI Mentor
Two arrays in step, and modify-by-index
for my $i (0 .. $#names) {
    printf "%-6s %+.2f\n", $names[$i], $slacks[$i];
}

for my $i (0 .. $#values) {
    $values[$i] = sprintf("%.2f", $values[$i]);
}

Walking two parallel arrays needs the shared index. Writing back into an array by index works too — though Chapter 9.2 shows foreach can modify the array directly, which is usually shorter.

4. The Loop Variable Is Scoped to the Loop

Azvya Education Pvt. Ltd.VLSI Mentor
restore.pl
use strict;
use warnings;

our $x = "before";

for $x ("a", "b") { }

print "after the loop, \$x = $x\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
after the loop, $x = before

$x came back. Even though the loop used a variable declared outside it and did not say my, foreach gave each iteration its own binding and restored the original afterwards.

That behaviour is called implicit localisation, and it has three consequences worth carrying forward:

  • Always write for my $b (...) anyway. Relying on the implicit version means the reader has to know this rule; my says it outright, and it is what use strict wants in the ordinary case.
  • The loop variable is not usable after the loop. With for my $b (...), $b does not exist outside. If you need the last value, assign it to a variable declared before the loop.
  • It is why the closure trap from Chapter 7.11 does not apply to foreach. Each iteration gets a fresh binding, so closures created inside a foreach capture distinct values — measured there as 1 2 3, where a C-style for gave 3 3 3.

The same mechanism is what makes $_ safe in nested foreach loops, which is Chapter 9.7's subject.

5. Looping Over Things That Are Not Arrays

foreach takes a list, and anything that produces a list works.

Azvya Education Pvt. Ltd.VLSI Mentor
lists.pl
use strict;
use warnings;

my %report = (alu => -0.12, fifo => 0.34, regf => -0.05);
my $rows   = [ ["alu", -0.12], ["fifo", 0.34] ];

print "a literal list :";
print " $_" for ("a", "b");
print "\n";

print "a range        :";
print " $_" for 1 .. 4;
print "\n";

print "sorted keys    :";
print " $_" for sort keys %report;
print "\n";

print "a sorted slice :";
print " $_" for (sort { $report{$a} <=> $report{$b} } keys %report)[0 .. 1];
print "\n";

print "through a ref  :";
print " $_->[0]" for @$rows;
print "\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
a literal list : a b
a range        : 1 2 3 4
sorted keys    : alu fifo regf
a sorted slice : alu regf
through a ref  : alu fifo

Note sort keys %report rather than bare keys — Chapter 6.5 measured that hash order varies between runs, so any loop whose output is read or diffed needs the sort. And @$rows dereferences an array reference, which is Chapter 7.3's notation.

A range is not materialised as a list in a foreach with simple numeric endpoints — Perl optimises for (1 .. 1_000_000) into a counting loop, so it costs no memory. A range built from variables or used anywhere else does build the list.

6. Why This Matters in Engineering Work

Most loops want foreach, and writing it that way removes a class of bug rather than avoiding one. No index means no bound, no subscript and no arithmetic — so there is nothing to be off by one about.

The bound mistake fires on the inputs you test least. 0 .. scalar(@a) costs one silent undef on a populated array and one whole wrong iteration on an empty one. A report with zero failing blocks is normal, routine, and exactly the case that exercises it.

And an index-based loop that edits its own array is a real shape. Filtering violations in place by splice inside a counting loop visited three of five in the measurement above and reported success. Rebuilding with grep has no index to desynchronise.

7. Common Mistakes

0 .. scalar(@a). One iteration too many. Use 0 .. $#a.

0 .. @a - 1 where the array might be empty. That is 0 .. -1, which is correct — but written as 0 .. $#a it says so directly.

Using an index when you only need the item. Extra variable, extra bound, extra subscript.

splice or delete inside a counting loop over the same array. Elements get skipped. Iterate backwards, or rebuild with grep.

Appending to the array a C-style loop is iterating. The condition is re-checked each pass, so the loop grows.

Expecting the loop variable to survive the loop. foreach localises it, and my scopes it to the body.

for my $k (keys %h) where the output is read. Add sort; hash order varies per run.

8. Exercise

Exercise 1 — three forms. Walk a three-element array with foreach, with 0 .. $#a, and with a C-style for. Confirm all three reach the same elements and note which gave you the index.

Exercise 2 — the off-by-one. Run both bounds over the same array with a defined test in the body. Record the two output lines, then remove the defined test and record what reaches STDERR and what the exit status is.

Exercise 3 — the empty array. Repeat Exercise 2 on an empty array. Report the iteration count for foreach, for 0 .. $#a, and for 0 .. scalar(@a). Print $#a and explain the third result.

Exercise 4 — when the index earns it. Write three loops: backwards over the values, every second element, and backwards with the index. Say which of the three did not need the counting form.

Exercise 5 — two arrays in step. Build @names and @slacks of the same length and print them side by side with one printf per row. Then make @slacks one element shorter and record what happens.

Exercise 6 — the shrinking array. Reproduce §3's splice loop. Record which elements were visited and which were left. Then fix it twice — once by iterating backwards, once with grep — and confirm both visit all five.

Exercise 7 — implicit localisation. Declare our $x = "before", loop with for $x (...), and print $x afterwards. Then do the same with for my $x (...) and try to print $x after the loop; record the error.

Exercise 8 — lists that are not arrays. Loop over a literal list, a range, sort keys %h, and an array reference with @$ref. Then loop over bare keys %h twice and say whether the order was the same.

9. Summary

  • foreach walks a list and hands you the item; the C-style for counts. for and foreach are the same keyword.
  • foreach is the default. No index means no bound, no subscript and no arithmetic to get wrong.
  • $#array is the last index; scalar(@array) is the count. They differ by exactly one, always.
  • 0 .. scalar(@a) runs one iteration too far, reading a silent undef — measured, with empty STDERR.
  • On an empty array $#a is -1, so 0 .. $#a runs zero times while 0 .. scalar(@a) runs once.
  • In a C-style condition use $i < @array — the array in numeric context is its count, so no scalar() is needed.
  • The counting form earns its place for a step other than 1, for two arrays in step, and for modifying by index.
  • splice inside a counting loop over the same array skips elements — measured 3 of 5 visited, exit 0. Iterate backwards or rebuild with grep.
  • foreach implicitly localises its loop variable, restoring an outer variable afterwards — which is why closures created inside a foreach capture distinct values.
  • Sort keys whenever the loop's output is read or diffed.

Chapter 9.2 takes up the consequence of that loop variable being a name for the array element rather than a copy of it: assigning to it changes the array.

Where this fits

Part of the Perl curriculum.