perl.gg / hidden-gems

<!-- category: hidden-gems -->

The continue Block

2026-05-09

Perl has a finally for loops. It runs after every iteration, even when you next. Almost nobody knows it exists.
for my $i (1 .. 5) { next if $i == 3; say "Processing $i"; } continue { say " (iteration $i done)"; }
Processing 1 (iteration 1 done) Processing 2 (iteration 2 done) (iteration 3 done) Processing 4 (iteration 4 done) Processing 5 (iteration 5 done)
See iteration 3? The main body was skipped by next, but the continue block still ran. That's the whole point. It's code that executes after every iteration, no matter how the iteration ended.

Unless you use last or redo. Those skip it. But next? The continue block catches it. Every time.

Part 1: THE BASIC SYNTAX

A continue block attaches to any loop. It goes right after the closing brace of the loop body:
while ($condition) { # loop body } continue { # runs after every iteration }
for my $item (@list) { # loop body } continue { # runs after every iteration }
foreach (@data) { # loop body } continue { # runs after every iteration }
It works with while, for, foreach, and until. The syntax is always the same: the continue keyword and a block, immediately after the loop's closing brace.

Part 2: WHEN IT RUNS AND WHEN IT DOESN'T

Three loop control keywords. Three behaviors:
KEYWORD CONTINUE BLOCK RUNS? WHAT HAPPENS ------- --------------------- --------------------------- next YES Skip rest of body, run continue, next iteration last NO Exit loop entirely, skip continue redo NO Restart iteration from top, skip continue
Let's prove it:
for my $n (1 .. 5) { if ($n == 2) { say " next on $n"; next; } if ($n == 4) { say " last on $n"; last; } say " body: $n"; } continue { say " continue: $n"; } say "loop done";
body: 1 continue: 1 next on 2 continue: 2 body: 3 continue: 3 last on 4 loop done
Iteration 1 and 3: normal flow, continue runs. Iteration 2: next skipped the body, but continue still ran. Iteration 4: last exited the loop, continue did not run. Iteration 5: never reached because last already exited.

Part 3: THE C FOR-LOOP CONNECTION

If you know C, this should feel familiar. The C for loop has three parts:
for (init; test; increment) { body; }
The third part (increment) runs after every iteration, even when you use continue (C's equivalent of Perl's next). Perl's continue block is exactly that third expression, but for any loop, not just for.

A C-style counted loop in Perl:

my $i = 0; while ($i < 10) { next if $i == 5; # skip 5 say $i; } continue { $i++; # always increments, even on next }
Without the continue block, next would skip $i++, and you'd get an infinite loop when $i hits 5. The continue block guarantees the counter always advances. This is the original use case.

Part 4: LOOP COUNTERS

The most practical use. You want a counter that tracks how many iterations have happened, regardless of next:
my $processed = 0; my $skipped = 0; for my $file (@files) { unless (-f $file) { $skipped++; next; } process_file($file); $processed++; } continue { if (($processed + $skipped) % 100 == 0) { say "Progress: $processed processed, $skipped skipped"; } } say "Final: $processed processed, $skipped skipped";
The progress report runs every 100 iterations, whether the file was processed or skipped. If you put the progress check inside the loop body, next would bypass it.

Part 5: PROGRESS REPORTING

For long-running loops, you want periodic status updates that never get skipped:
my $start = time(); my $count = 0; while (my $line = <$fh>) { chomp $line; # various reasons to skip next if $line =~ m~^#~; # comments next if $line =~ m~^\s*$~; # blank lines next unless $line =~ m~\t~; # needs a tab process($line); } continue { $count++; if ($count % 10_000 == 0) { my $elapsed = time() - $start; printf STDERR "\r%d lines in %ds (%.0f lines/sec)", $count, $elapsed, $count / ($elapsed || 1); } } say STDERR "\nDone. $count total lines.";
Three next statements in the body. The progress counter in continue sees every line, skipped or not. Without continue, you'd need to increment the counter before every next call, which means three copies of the same code.

Part 6: RESOURCE CLEANUP

Clean up per-iteration resources even when the body bails early:
for my $url (@urls) { my $tmpfile = "/tmp/download_$$.tmp"; open my $fh, '>', $tmpfile or do { warn "Cannot write $tmpfile: $!\n"; next }; my $response = fetch($url); unless ($response->{success}) { warn "Failed: $url\n"; next; } print $fh $response->{body}; close $fh; process($tmpfile); } continue { # always clean up the temp file, even on next my $tmpfile = "/tmp/download_$$.tmp"; unlink $tmpfile if -e $tmpfile; }
Two next calls in the body. Both skip the cleanup at the end. But the continue block catches them. The temp file always gets deleted.

Is this better than a separate cleanup function? Maybe not. But it keeps the cleanup visually attached to the loop it belongs to. No hunting for a subroutine definition. The intent is obvious: "after every iteration, no matter what, do this."

Part 7: THE WHILE-READLINE PATTERN

There's a famous Perl idiom using continue with while (<>):
while (<>) { # process lines from files in @ARGV chomp; say "Line $.: $_" if m~ERROR~; } continue { close ARGV if eof; # reset $. between files }
The close ARGV if eof trick resets the line counter $. when Perl finishes one file and moves to the next. Without it, $. just keeps incrementing across all files. With it, $. restarts at 1 for each file.

This is documented in perldoc -f eof and it specifically recommends the continue block for it. It's one of the rare places where the official Perl documentation says "use continue."

Part 8: COMBINING WITH LOOP LABELS

continue works with labeled loops:
OUTER: for my $dir (@dirs) { opendir my $dh, $dir or next; INNER: while (my $file = readdir $dh) { next INNER if $file =~ m~^\.~; next OUTER unless -f "$dir/$file"; process("$dir/$file"); } continue { # this continue belongs to INNER } closedir $dh; } continue { # this continue belongs to OUTER say "Finished directory: $dir"; }
Each loop can have its own continue block. The next INNER triggers the inner continue. The next OUTER skips the inner continue and triggers the outer one.

Part 9: WHY ALMOST NOBODY USES IT

The continue block is documented. It's been in Perl since version 5. It works exactly as advertised. And almost nobody uses it.

Why?

Most people don't know it exists. It's buried in perlsyn between loop modifiers and basic loops. No tutorial teaches it. No beginner book highlights it. It's there, but invisible.

The workaround is easy. Instead of continue, most people just put the code before every next:

for my $item (@list) { $count++; # before every next next unless valid($item); $count_again++; # whoops, wrong count process($item); }
This works until you forget one next path. Then your counter is wrong and you spend thirty minutes debugging.

Perl culture moved on. Modern Perl emphasizes subroutines, closures, and higher-order functions over loop mechanics. The continue block is a procedural-era construct. It's not wrong, it's just old-school.

But for its intended use case, loop-invariant post-iteration code, nothing else is as clean. Not a separate function call. Not duplicated code before every next. The continue block is the right tool.

Part 10: THE LOOP LIFECYCLE

START | v +---------+ | test |---false---> EXIT LOOP | cond | +---------+ | true | v +---------+ | BODY |---last---> EXIT LOOP (skip continue) | |---redo---> back to BODY (skip continue) | |---next--+ +---------+ | | | v v +-----------+ | CONTINUE | <-- runs on normal end AND on next +-----------+ | v (back to test) .--. |o_o | "next skips the body. |:_/ | It does not skip me." // \ \ -- continue block (| | ) /'\_ _/`\ \___)=(___/
The continue block is Perl's per-iteration cleanup mechanism. It runs after every iteration that doesn't exit the loop entirely. next triggers it. Normal flow triggers it. Only last and redo bypass it.

It solves a real problem: code that must run after every iteration, regardless of how the iteration ended. Counters, progress bars, cleanup, resource release, line-number resets. All of these belong in continue.

Most Perl programmers will go their entire career without writing one. But knowing it exists means you have a clean answer for the next time you find yourself copy-pasting a counter increment before every next statement in a loop.

perl.gg