perl.gg / hidden-gems

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

The redo Keyword

2026-05-02

You know next. 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."

for my $item (@list) { # ... processing ... redo if $need_to_retry; # restart THIS iteration }
When 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.
#!/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";
Without 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.
#!/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); } }
Wait. That used a labeled block, not a for loop. Yes. 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! } }
count is 0 count is 1 count is 10
After 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 you redo unconditionally, you loop forever:
for my $i (1 .. 10) { say $i; redo; # infinite loop! $i is always 1, 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).

Always pair redo with a guard:

my $retries = 0; for my $i (1 .. 10) { $retries++; if (needs_retry() && $retries < 5) { redo; } $retries = 0; # reset for next iteration process($i); }
The $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 for redo. Display the menu. Get input. If invalid, show the menu again. Same iteration. No counter to manage.
#!/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.";
The labeled block 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"; }
OK: 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
The 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 replace redo with a while(1) loop and last:
# 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(); } }
The 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:

for 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 }
Try expressing that with while(1). You end up with nested conditions or flag variables. redo keeps it flat.

Part 9: REDO WITH LABELS

Like next 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