Beginner

Control Flow in Perl

Learn conditionals, loops, and loop control in Perl - if/elsif/else, unless, while/until, foreach, statement modifiers, and labeled loops with Docker-ready examples

Control flow determines the order in which your program’s statements run. Perl gives you the familiar if/elsif/else conditionals and while/for loops you’d expect, but it also adds its own flavor: negated keywords (unless, until), statement modifiers that put the condition after the action, and a context-sensitive design that makes reading code feel closer to English.

True to its motto—“There’s More Than One Way To Do It”—Perl rarely forces a single style. You can write a verbose block for clarity or collapse a check into a one-line statement modifier for brevity. This flexibility is a hallmark of Perl as a dynamic, multi-paradigm language.

This tutorial covers conditionals, the full set of loop constructs, loop control with last/next/redo, labeled loops for nested iteration, and Perl’s idiomatic replacements for the switch statement it never built in. Every example uses strict and warnings, which catch common mistakes and are considered mandatory in modern Perl.

Conditionals: if, elsif, else, unless

Perl’s conditionals branch on the truthiness of an expression. In Perl, 0, the empty string "", "0", and undef are false; everything else is true.

Create a file named control_flow_conditionals.pl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/usr/bin/env perl
use strict;
use warnings;
use v5.10;

my $score = 85;

# Standard if / elsif / else chain (note: "elsif", not "elseif")
if ($score >= 90) {
    say "Grade: A";
} elsif ($score >= 80) {
    say "Grade: B";
} elsif ($score >= 70) {
    say "Grade: C";
} else {
    say "Grade: F";
}

# unless is "if not" - runs the block when the condition is false
my $logged_in = 0;
unless ($logged_in) {
    say "Please log in";
}

# Statement modifiers: put the condition AFTER the statement
say "You passed!" if $score >= 60;
say "Try again"   unless $score >= 60;   # suppressed: score is 85

# Ternary conditional operator (?:)
my $status = $score >= 60 ? "pass" : "fail";
say "Status: $status";

The elsif spelling is a frequent surprise for newcomers—Perl does not recognize elseif or else if. Statement modifiers (say "..." if $cond) are idiomatic Perl: they read naturally and avoid an extra block when you only have one statement to guard.

Loops: foreach, for, while, until

Perl offers several looping constructs. foreach (which can be spelled for) iterates over a list, the C-style for counts with an explicit index, while repeats as long as a condition holds, and until is its negated twin.

Create a file named control_flow_loops.pl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/usr/bin/env perl
use strict;
use warnings;
use v5.10;

# foreach iterates over each element of a list
my @colors = ("red", "green", "blue");
foreach my $color (@colors) {
    say "Color: $color";
}

# C-style for loop with an explicit counter
for (my $i = 1; $i <= 3; $i++) {
    say "Count: $i";
}

# The range operator (..) generates a list - great with foreach
for my $n (1..5) {
    print "$n ";
}
print "\n";

# while repeats while the condition is true
my $countdown = 3;
while ($countdown > 0) {
    say "T-minus $countdown";
    $countdown--;
}

# until repeats UNTIL the condition becomes true (opposite of while)
my $i = 0;
until ($i >= 2) {
    say "i is $i";
    $i++;
}

foreach is the most common loop in idiomatic Perl. The range operator 1..5 builds the list (1, 2, 3, 4, 5) lazily, so you rarely need a manual C-style counter. Reach for until when negating a while condition would make the code harder to read.

Loop control: last, next, redo, and labels

Perl controls loop iteration with last (exit the loop, like break), next (skip to the next iteration, like continue), and redo (restart the current iteration without re-testing the condition). Loops can also carry labels, letting you control an outer loop from inside a nested one.

Create a file named control_flow_control.pl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/env perl
use strict;
use warnings;
use v5.10;

# next skips the rest of the current iteration
foreach my $n (1..6) {
    next if $n % 2 == 0;   # skip even numbers
    say "Odd: $n";
}

# last exits the loop entirely
foreach my $n (1..10) {
    last if $n > 3;
    say "Number: $n";
}

# Labeled loops control which loop next/last applies to
OUTER: for my $row (1..3) {
    for my $col (1..3) {
        next OUTER if $col == 2;   # jump to the next row
        say "row=$row col=$col";
    }
}

Labels (by convention written in UPPERCASE) attach to a loop and let next LABEL or last LABEL target an outer loop directly. Without the label, next would only skip the inner $col loop. This is far cleaner than the flag-variable gymnastics other languages require for breaking out of nested loops.

The missing switch: dispatch tables and the for-topicalizer

Perl has no native switch/case statement. (The experimental given/when was deprecated in Perl 5.38 and should be avoided.) Instead, Perl programmers reach for two idioms: a dispatch hash mapping keys to code references, and the for-as-topicalizer trick that aliases $_ so a chain of regex tests reads like a switch.

Create a file named control_flow_switch.pl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/usr/bin/env perl
use strict;
use warnings;
use v5.10;

# Dispatch table: map each command to an anonymous subroutine
my %actions = (
    start => sub { say "Starting up..." },
    stop  => sub { say "Shutting down..." },
    pause => sub { say "Pausing..." },
);

my $command = "stop";
if (my $action = $actions{$command}) {
    $action->();                       # call the matched code reference
} else {
    say "Unknown command: $command";
}

# for-as-switch: aliasing $_ lets a chain of tests act like case labels
for ($command) {
    if    (/^start$/) { say "matched start" }
    elsif (/^stop$/)  { say "matched stop"  }
    else              { say "no match"      }
}

The dispatch hash scales well: adding a new command is just one more key, and lookup stays fast regardless of how many cases you have. The for ($command) { ... } idiom temporarily makes $command the topic variable $_, so each regex (/^stop$/) matches against it automatically—Perl’s flexible answer to the switch statement it never needed.

Running with Docker

Run each example with the official Perl image. No local Perl installation required.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Pull the official image
docker pull perl:5.40-slim

# Run the conditionals example
docker run --rm -v $(pwd):/app -w /app perl:5.40-slim perl control_flow_conditionals.pl

# Run the loops example
docker run --rm -v $(pwd):/app -w /app perl:5.40-slim perl control_flow_loops.pl

# Run the loop control example
docker run --rm -v $(pwd):/app -w /app perl:5.40-slim perl control_flow_control.pl

# Run the switch-alternatives example
docker run --rm -v $(pwd):/app -w /app perl:5.40-slim perl control_flow_switch.pl

Expected Output

control_flow_conditionals.pl:

Grade: B
Please log in
You passed!
Status: pass

control_flow_loops.pl:

Color: red
Color: green
Color: blue
Count: 1
Count: 2
Count: 3
1 2 3 4 5 
T-minus 3
T-minus 2
T-minus 1
i is 0
i is 1

control_flow_control.pl:

Odd: 1
Odd: 3
Odd: 5
Number: 1
Number: 2
Number: 3
row=1 col=1
row=2 col=1
row=3 col=1

control_flow_switch.pl:

Shutting down...
matched stop

Key Concepts

  • elsif, not else if — Perl spells the chained conditional elsif; elseif and else if are syntax errors.
  • Negated keywordsunless is if not, and until is while not. Use them when a negative condition reads more naturally than a ! would.
  • Statement modifiers — Appending if, unless, while, or until to a single statement (say "Hi" if $debug) is concise, idiomatic Perl.
  • Truthiness0, "", "0", and undef are false; everything else is true. There is no dedicated boolean type.
  • foreach plus rangesfor my $n (1..10) is the most common Perl loop; the range operator .. builds the list for you, so manual C-style counters are rarely needed.
  • last, next, redo — Perl’s loop-control verbs replace break/continue, and labeled loops let them target an outer loop in nested iteration.
  • No native switch — Use a dispatch hash of code references for scalable branching, or the for ($var) { /regex/ } topicalizer idiom; avoid the deprecated given/when.
  • use strict; use warnings; — Always enable both in real scripts; they catch undeclared variables, typos, and many runtime surprises before they bite.

Running Today

All examples can be run using Docker:

docker pull perl:5.40-slim
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining