Skip to content
VLSI Mentor

Perl Part I · Module 9 · Loops

do-while and do-until

Every loop so far tests its condition before running the body, which means a condition that is false at the start produces zero iterations. Sometimes that is wrong: a retry has to attempt something before it can know whether to retry, and a read-until-sentinel has to read before it can check for the sentinel. The do-while form moves the test to the bottom, so the body always runs at least once. It is the least used loop form in Perl and the one with the sharpest restriction, because it is not implemented as a loop at all — it is a block with a condition attached — and the two keywords you would reach for to control it are not merely ineffective inside it, they are fatal.

Intermediate13 min readPerlPart IControl FlowLoops

Part I · Module 9 · Chapter 9.4 · do-while and do-until

1. The Body Runs First

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

my $i = 10;

do { print "do-while body ran, i=$i\n" } while ($i < 5);

my $k = 10;
while ($k < 5) { print "never\n" }
print "the plain while ran zero times\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
do-while body ran, i=10
the plain while ran zero times

Same condition, same starting value, and the two forms disagree about whether to run at all. $i < 5 was false before either loop started — the do body ran anyway, and the plain while did not.

do-until works the same way:

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

my $i = 10;

do { print "do-until body ran, i=$i\n" } until ($i > 5);
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
do-until body ran, i=10

$i > 5 was already true at entry — which for until means "stop" — and the body still ran once.

FormMinimum iterationsCondition tested
while (COND) { ... }0before each pass
until (COND) { ... }0before each pass
do { ... } while (COND);1after each pass
do { ... } until (COND);1after each pass

Note the semicolon after the condition. It is required, because — as §3 explains — this is a statement with a modifier attached rather than a block construct.

2. The Shape It Is Actually For

A retry is the honest use case: you cannot know whether to retry before trying.

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

my @responses = (0, 0, 1);      # succeeds on the third attempt
my $attempt   = 0;
my $ok        = 0;

do {
    $attempt++;
    $ok = $responses[$attempt - 1] // 0;
    print "  attempt $attempt: ", ($ok ? "ok" : "not yet"), "\n";
} while (!$ok && $attempt < 5);

printf "finished after %d attempt(s), ok=%d\n", $attempt, $ok;
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
  attempt 1: not yet
  attempt 2: not yet
  attempt 3: ok
finished after 3 attempt(s), ok=1

Three things about that loop are worth copying:

  • The first attempt is unconditional, which is what makes the form fit. A plain while (!$ok && ...) would also work here because $ok starts false — but only because it was initialised to false on purpose, and that initialisation exists solely to make the pre-test loop behave like a post-test one.
  • $attempt < 5 is in the condition. Chapter 9.8 is the chapter on this, and it matters more here than in other loops: a retry whose condition depends on something external is the classic shape that never terminates.
  • // 0 supplies a default past the end of the list rather than letting undef warn — Chapter 8.5 §4's operator.

The same shape covers reading until a sentinel, prompting until the input is valid, and polling a file that may not exist yet.

3. It Is Not a Loop, and the Error Message Says So

Azvya Education Pvt. Ltd.VLSI Mentor
dowhile-last.pl
use strict;
use warnings;

my $i = 0;

do {
    $i++;
    last if $i > 2;
} while ($i < 10);

print "i=$i\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output — STDERR
Can't "last" outside a loop block at dowhile-last.pl line 8.
Azvya Education Pvt. Ltd.VLSI Mentor
Measured exit status
255
Azvya Education Pvt. Ltd.VLSI Mentor
dowhile-next.pl
use strict;
use warnings;

my $i = 0;

do {
    $i++;
    next if $i < 3;
} while ($i < 5);

print "i=$i\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output — STDERR
Can't "next" outside a loop block at dowhile-next.pl line 8.
Azvya Education Pvt. Ltd.VLSI Mentor
Measured exit status
255

Read the wording: "outside a loop block". Perl is not saying last is disallowed here as a special case — it is saying the enclosing construct is not a loop, so there is nothing for last to leave.

That is literally true. do { ... } is an expression that evaluates a block and yields its last value, and the while on the end is the statement modifier from Chapter 8.3. So do { } while ( ) is "evaluate this expression repeatedly while the condition holds" — which produces loop-shaped behaviour out of parts that are not a loop. Chapter 8.3 §7 measured that a bare block cannot take a modifier at all, and this is the reason do { } can: it is an expression, not a block statement.

4. A Bare Block Is a Loop

The quirk is worth knowing because it is the documented workaround.

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

my $i = 0;

LOOP: {
    $i++;
    redo LOOP if $i < 3;
}

print "bare block with redo: i=$i\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
bare block with redo: i=3
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet

A bare block is a loop that executes exactly once, and the loop-control keywords all work inside it:

Keyword in a bare blockEffect
lastleave the block
nextleave the block — measured i=1, since there is no next iteration
redorun the block again from the top

So redo turns a bare block into a loop that repeats as many times as you like, with the test wherever you put it. That gives you do-while's test-at-the-bottom behaviour and working loop control.

It is not a common idiom and it will make some readers pause, so prefer while (1) with last for anything another person will maintain. Know this one so that redo LOOP in inherited code reads correctly, and for the case where last inside a do block has to be fixed quickly.

Chapter 9.5 covers redo properly, including why it is rare and what it does to the loop list.

5. Why This Matters in Engineering Work

The first-attempt-unconditional shape is real. Polling for a file a tool is about to write, retrying a flaky network mount, prompting until an answer parses — each must act before it can test. do-while expresses that directly.

But the restriction usually outweighs the convenience. The moment a retry needs to abandon early on a fatal error — which is most retries worth writing — you need last, and inside a do block that is a runtime death. Starting with while (1) avoids the rewrite.

And the failure mode is the dangerous kind. A last on an error path that never fires in testing compiles, passes perl -c, and runs correctly for months. The thing that triggers it is the error it was written to handle.

6. Common Mistakes

Using last or next inside a do { } while. Fatal at run time: Can't "last" outside a loop block.

Assuming that failure is caught at compile time. It is not, and perl -c passes. A guarded last ships.

Forgetting the semicolon after do { ... } while (COND);. It is a statement, not a block construct.

Reaching for do-while when the body is non-trivial. Use while (1) with an explicit last so loop control is available.

Expecting do { } until (COND) to skip the body when the condition is already true. It always runs once.

Writing a do-while retry with no attempt bound. It may never finish — Chapter 9.8 covers it.

Reading a bare block as not-a-loop. It is one, which is why redo works there.

7. Exercise

Exercise 1 — one pass minimum. With $i = 10, write do { ... } while ($i < 5); and the equivalent plain while. Record how many times each body ran.

Exercise 2 — the until form. With $i = 10, write do { ... } until ($i > 5);. Record whether the body ran and explain it in one sentence.

Exercise 3 — the retry. Build §2's retry over (0, 0, 1) and confirm it finishes on attempt 3. Then change the list to (0, 0, 0, 0, 0, 0) and confirm the attempt bound stops it at 5.

Exercise 4 — last inside. Add last if $i > 2; to a do-while body. Record the exact error message and the exit status. Then do the same with next.

Exercise 5 — the one that ships. Put a last inside a do-while behind a condition that is false, and confirm the script runs cleanly. Then run perl -c on it and record the result. Finally make the condition true and record the failure.

Exercise 6 — the real loop. Rewrite Exercise 5 as while (1) with an explicit last. Confirm it works in both the false and true cases.

Exercise 7 — the bare block. Write LOOP: { $i++; redo LOOP if $i < 3; } and confirm $i ends at 3. Then replace redo with next and record what happens.

Exercise 8 — which form. For each of these, say which loop form you would write and why: reading a file to EOF; prompting until the input parses; iterating a list of blocks; retrying a mount up to five times with an early exit on a permissions error.

8. Summary

  • do { ... } while (COND); runs the body once before testing, so the minimum is one iteration where while and until have a minimum of zero.
  • do { ... } until (COND); is the same, with the condition reversed.
  • The trailing semicolon is required — it is a statement with a modifier, not a block construct.
  • A do block is not a loop. last and next inside it are fatal: Can't "last" outside a loop block, exit 255.
  • That error is at run time, not compile time. perl -c passes, and a last on an untaken error path ships and fails later.
  • A bare block is a loop that runs once, so last, next and redo all work there — redo LOOP gives test-at-the-bottom with working loop control.
  • Prefer while (1) with an explicit last for anything beyond a trivial retry. It trades the implicit first pass for loop control, which is usually worth it.
  • Bound every retry. A do-while waiting on something external may never finish.

Chapter 9.5 covers the three loop-control keywords themselves — next, last and the rarely-seen redo — including what redo does to the loop's position.

Where this fits

Part of the Perl curriculum.