<!-- category: hidden-gems -->
The redo Keyword
You knownext. Skip to the next iteration. You know last. Exit
the loop entirely. But Perl has a third loop control keyword that
almost nobody uses.
redo.
It jumps back to the top of the loop body. No condition check. No iterator advance. Just "do this iteration again from the beginning."
Whenfor my $item (@list) { # ... processing ... redo if $need_to_retry; # restart THIS iteration }
redo fires, $item keeps its current value. The for
iterator does not advance. The loop condition is not re-evaluated.
Perl just jumps to the opening brace and runs the body again.
It is like next without the "next" part. Same iteration. Same
data. Try again.
Part 1: HOW REDO DIFFERS FROM NEXT
This is the crucial distinction. Three keywords, three different behaviors:for my $i (1 .. 5) { say "Processing $i"; next if $i == 3; # skip to $i = 4 say "Finished $i"; }
next skips the rest of the loop body and advances the iterator.
The loop moves on to the next value.
for my $i (1 .. 5) { say "Processing $i"; last if $i == 3; # exit the loop entirely say "Finished $i"; }
last exits the loop. Done. Over. No more iterations.
my $attempts = 0; for my $i (1 .. 5) { $attempts++; say "Processing $i (attempt $attempts)"; if ($i == 3 && $attempts < 6) { redo; # restart this iteration of $i = 3 } say "Finished $i"; }
redo restarts the current iteration. $i is still 3. The loop
body runs again from the top. The iterator has not moved.
next: skip rest of body, advance iterator, check condition last: exit loop immediately redo: restart body, same iterator value, no condition check
Part 2: INPUT VALIDATION LOOPS
The classic use case. Ask the user for input. If it is bad, ask again. Do not advance. Do not move on. Just keep asking until they get it right.Without#!/usr/bin/env perl use strict; use warnings; use feature 'say'; my @questions = ( "What is your name? ", "What is your age? ", "What is your email? ", ); my @answers; for my $q (@questions) { print $q; my $answer = <STDIN>; chomp $answer; if ($answer eq '') { say "That cannot be blank. Try again."; redo; # ask the same question again } push @answers, $answer; } say "Got: @answers";
redo, you would need a nested while loop inside the
for loop. Or you would need to decrement the loop counter manually.
Or you would restructure the whole thing. With redo, you just say
"nope, try again" and the loop naturally repeats the current
question.
Part 3: RETRY-ON-FAILURE
Network request failed? Database query timed out? File locked by another process?redo is your retry mechanism.
Wait. That used a labeled block, not a for loop. Yes.#!/usr/bin/env perl use strict; use warnings; use feature 'say'; my @urls = ( 'https://api.example.com/users', 'https://api.example.com/posts', 'https://api.example.com/comments', ); my $max_retries = 3; for my $url (@urls) { my $tries = 0; FETCH: { $tries++; say "Fetching $url (attempt $tries)"; my $response = fetch_url($url); if (!$response && $tries < $max_retries) { say "Failed. Retrying in 2 seconds..."; sleep 2; redo FETCH; } if (!$response) { say "Giving up on $url after $max_retries attempts"; next; # move to next URL } process($response); } }
redo works
with any block structure that has a label, not just loops. A bare
block with a label becomes a "retry" block. redo jumps back to
the top of it.
Part 4: REDO WITH WHILE LOOPS
redo in a while loop skips the condition check. This is
important. The condition is not re-evaluated.
my $count = 0; while ($count < 3) { say "count is $count"; $count++; if ($count == 2) { $count = 10; # should make the while condition false redo; # but redo skips the condition check! } }
Aftercount is 0 count is 1 count is 10
redo, Perl does not check $count < 3. It just re-enters
the loop body. $count is 10, which should have ended the loop,
but redo bypassed the guard. The loop body runs one more time
with $count = 10, then $count becomes 11, the loop body
finishes normally, the condition is finally checked, and the loop
ends.
This is both the power and the danger. redo trusts you to know
what you are doing. It does not protect you from infinite loops
or invalid state.
Part 5: THE INFINITE LOOP DANGER
No condition check means no automatic exit. If youredo
unconditionally, you loop forever:
The iterator never advances. The condition is never checked. This runs until you kill the process or your machine runs out of memory (it won't, because this is just stack-free looping, but it will run forever).for my $i (1 .. 10) { say $i; redo; # infinite loop! $i is always 1, forever }
Always pair redo with a guard:
Themy $retries = 0; for my $i (1 .. 10) { $retries++; if (needs_retry() && $retries < 5) { redo; } $retries = 0; # reset for next iteration process($i); }
$retries < 5 guard ensures the redo eventually stops. Never
use redo without a counter, a flag, or some other mechanism that
guarantees progress.
Part 6: MENU LOOPS
Interactive menus are a natural fit forredo. Display the menu.
Get input. If invalid, show the menu again. Same iteration. No
counter to manage.
The labeled block#!/usr/bin/env perl use strict; use warnings; use feature 'say'; my @items = qw(backup restore status quit); MENU: { say "\n=== Menu ==="; for my $i (0 .. $#items) { say " $i) $items[$i]"; } print "Choice: "; my $choice = <STDIN>; chomp $choice; if ($choice !~ m~^\d+$~ || $choice > $#items) { say "Invalid choice. Try again."; redo MENU; } my $action = $items[$choice]; last MENU if $action eq 'quit'; say "Running: $action"; do_action($action); redo MENU; # show menu again after action completes } say "Goodbye.";
MENU: becomes a reusable menu frame. redo MENU
restarts it. last MENU exits it. No while(1) needed. The block
itself is the loop structure, controlled entirely by redo and
last.
Part 7: DATA CLEANING WITH RETRY
Process a list of records. If a record is malformed, try to fix it and reprocess.redo means "I fixed the data, run the same logic
again on the same element."
#!/usr/bin/env perl use strict; use warnings; use feature 'say'; my @records = ( "Alice,30,Toronto", "Bob,,Montreal", # missing age "Carol,25,Vancouver", ",40,Ottawa", # missing name ); for my $rec (@records) { my @fields = split m~,~, $rec; if (@fields != 3 || grep { $_ eq '' } @fields) { say "Bad record: '$rec'"; # attempt to fix $fields[0] = "UNKNOWN" if !$fields[0] || $fields[0] eq ''; $fields[1] = 0 if !$fields[1] || $fields[1] eq ''; $fields[2] = "N/A" if !$fields[2] || $fields[2] eq ''; $rec = join(",", @fields); say "Fixed to: '$rec'"; redo; # reprocess with fixed data } say "OK: $rec"; }
TheOK: Alice,30,Toronto Bad record: 'Bob,,Montreal' Fixed to: 'Bob,0,Montreal' OK: Bob,0,Montreal OK: Carol,25,Vancouver Bad record: ',40,Ottawa' Fixed to: 'UNKNOWN,40,Ottawa' OK: UNKNOWN,40,Ottawa
redo sends the fixed record back through the same validation
logic. If the fix worked, it passes. If not, it gets fixed again.
The loop only advances when a record is clean.
Part 8: COMPARISON TO WHILE(1) WITH LAST
You can always replaceredo with a while(1) loop and last:
The# with redo for my $item (@items) { do_stuff($item); redo if needs_retry(); } # without redo (equivalent) for my $item (@items) { while (1) { do_stuff($item); last unless needs_retry(); } }
while(1) version is arguably clearer to beginners. But it
adds nesting and introduces a loop that exists only to simulate
a feature that already exists in the language.
redo expresses intent directly. "Redo this iteration." Not "loop
forever until we explicitly break." The semantics are different even
if the behavior is the same.
In complex loops with multiple redo points, the while(1) version
becomes unwieldy:
Try expressing that withfor my $item (@items) { redo if fixup_step_1($item); # fixed something, recheck redo if fixup_step_2($item); # fixed something else, recheck process($item); # all clean, proceed }
while(1). You end up with nested
conditions or flag variables. redo keeps it flat.
Part 9: REDO WITH LABELS
Likenext and last, redo can target a labeled loop. This
matters when you have nested loops:
#!/usr/bin/env perl use strict; use warnings; use feature 'say'; my @batches = ([1, 2, 3], [4, 5, 6]); BATCH: for my $batch (@batches) { my $batch_retries = 0; for my $item (@$batch) { say "Processing item $item"; if (batch_corrupted($batch)) { $batch_retries++; if ($batch_retries < 3) { say "Batch corrupted, reloading..."; reload_batch($batch); redo BATCH; # restart entire batch } say "Batch failed after $batch_retries attempts"; next BATCH; # skip to next batch } process($item); } }
redo BATCH restarts the outer loop at the same batch. next BATCH
skips to the next batch. The label tells Perl which loop to control.
Without the label, redo would restart the inner loop.
Part 10: THE FORGOTTEN KEYWORD
.--. |o_o | "Same iteration. Same data. |:_/ | Different outcome. // \ \ That's the hope, anyway." (| | ) /'\_ _/`\ \___)=(___/
redo is Perl's least-known loop control keyword. Most programmers
learn next and last and never discover the third option. It sits
in the documentation, quietly waiting for the day you need to retry
something without restructuring your loop.
The sweet spot is retry logic. Input validation. Data cleaning. Menu
systems. Any place where "try again with the same element" is the
natural response to a problem. redo expresses that intent in a
single word.
The danger is infinite loops. Always have a guard. A counter. A
flag. Something that guarantees the redo will eventually stop
firing. Trust the keyword, but verify your exit condition.
It is not a keyword you use every day. But when you need it, nothing
else fits as cleanly. next moves forward. last gives up. redo
says "not yet." And sometimes, "not yet" is exactly right.
perl.gg