Beginner

Control Flow in Ada

Learn conditionals, loops, and case statements in Ada with practical Docker-ready examples using the GNAT compiler

Control flow defines the order in which statements execute in a program. Ada provides a rich set of control structures designed with safety and readability in mind, reflecting its heritage as a language built for long-lived, mission-critical software.

As a strongly typed multi-paradigm language, Ada’s control flow constructs are notably explicit: every block has a matching end clause, loops can be named for clarity, and the case statement requires exhaustive coverage of all possible values. This verbosity is deliberate — it makes intent obvious to anyone reading the code years later and helps the compiler catch logical gaps early.

In this tutorial, you’ll learn how Ada handles conditionals with if/elsif/else, how the case statement enforces exhaustive matching, and the three forms of loops Ada provides: loop, while, and for. You’ll also see how to control loop execution with exit and how Ada’s named loops make complex iteration patterns readable.

If/Elsif/Else Statements

The if statement in Ada uses the keywords then, optional elsif branches, an optional else, and a closing end if. Note the spelling: it’s elsif, not elseif or else if.

Create a file named conditionals.adb:

 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
with Ada.Text_IO;
with Ada.Integer_Text_IO;

procedure Conditionals is
   Score : constant Integer := 87;
   Grade : Character;
begin
   if Score >= 90 then
      Grade := 'A';
   elsif Score >= 80 then
      Grade := 'B';
   elsif Score >= 70 then
      Grade := 'C';
   elsif Score >= 60 then
      Grade := 'D';
   else
      Grade := 'F';
   end if;

   Ada.Text_IO.Put ("Score: ");
   Ada.Integer_Text_IO.Put (Score, Width => 0);
   Ada.Text_IO.Put_Line (" -> Grade " & Grade);

   if Score >= 60 and Score <= 100 then
      Ada.Text_IO.Put_Line ("Result: Passing");
   end if;
end Conditionals;

Notice that Ada uses and, or, and not as boolean operators — not &&, ||, and !. For short-circuit evaluation, Ada provides and then and or else, which only evaluate the right-hand side when needed.

Case Statements

The case statement is Ada’s switch construct. Unlike C-style switches, Ada’s case requires that every possible value of the selector be covered — either by explicit when branches or by a when others clause. The compiler enforces this exhaustiveness check.

Create a file named case_example.adb:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
with Ada.Text_IO;

procedure Case_Example is
   type Day_Of_Week is (Mon, Tue, Wed, Thu, Fri, Sat, Sun);
   Today : constant Day_Of_Week := Wed;
begin
   case Today is
      when Mon | Tue | Wed | Thu | Fri =>
         Ada.Text_IO.Put_Line ("It's a weekday - time to work");
      when Sat | Sun =>
         Ada.Text_IO.Put_Line ("It's the weekend - relax!");
   end case;

   case Today is
      when Mon =>
         Ada.Text_IO.Put_Line ("Start of the work week");
      when Fri =>
         Ada.Text_IO.Put_Line ("Almost the weekend");
      when others =>
         Ada.Text_IO.Put_Line ("A regular day");
   end case;
end Case_Example;

The | operator combines multiple values in one branch, and ranges like when 1 .. 5 => work for discrete types. There is no fall-through — each branch ends implicitly without needing a break statement.

Loops: Basic, While, and For

Ada provides three loop forms, all built on the fundamental loop/end loop construct. The plain loop is an infinite loop you exit explicitly. Prefixing it with while adds a top-tested condition, and for provides counted iteration.

Create a file named loops.adb:

 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
36
37
38
39
40
with Ada.Text_IO;
with Ada.Integer_Text_IO;

procedure Loops is
   Counter : Integer := 1;
   Sum     : Integer := 0;
begin
   --  Plain loop with exit
   Ada.Text_IO.Put_Line ("Plain loop counting to 3:");
   loop
      Ada.Integer_Text_IO.Put (Counter, Width => 0);
      Ada.Text_IO.New_Line;
      exit when Counter >= 3;
      Counter := Counter + 1;
   end loop;

   --  While loop accumulating a sum
   Counter := 1;
   while Counter <= 5 loop
      Sum := Sum + Counter;
      Counter := Counter + 1;
   end loop;
   Ada.Text_IO.Put ("Sum 1..5 = ");
   Ada.Integer_Text_IO.Put (Sum, Width => 0);
   Ada.Text_IO.New_Line;

   --  For loop with a range
   Ada.Text_IO.Put_Line ("Squares from 1 to 4:");
   for I in 1 .. 4 loop
      Ada.Integer_Text_IO.Put (I * I, Width => 0);
      Ada.Text_IO.New_Line;
   end loop;

   --  Reverse for loop
   Ada.Text_IO.Put_Line ("Counting down:");
   for I in reverse 1 .. 3 loop
      Ada.Integer_Text_IO.Put (I, Width => 0);
      Ada.Text_IO.New_Line;
   end loop;
end Loops;

The for loop variable is implicitly declared, has the type of the range, and is read-only within the loop body — you cannot assign to I. This is one of Ada’s safety features: the loop counter cannot be accidentally modified to corrupt the iteration.

Named Loops and Exit Control

Ada lets you give loops names, which is invaluable when exiting nested loops. The exit statement can target a specific named loop, eliminating the need for sentinel variables or goto.

Create a file named named_loops.adb:

 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
with Ada.Text_IO;
with Ada.Integer_Text_IO;

procedure Named_Loops is
   Target  : constant Integer := 42;
   Found_R : Integer := 0;
   Found_C : Integer := 0;
   Grid    : constant array (1 .. 3, 1 .. 3) of Integer :=
     ((10, 20, 30),
      (40, 42, 50),
      (60, 70, 80));
begin
   Search :
   for Row in Grid'Range (1) loop
      for Col in Grid'Range (2) loop
         if Grid (Row, Col) = Target then
            Found_R := Row;
            Found_C := Col;
            exit Search;
         end if;
      end loop;
   end loop Search;

   if Found_R /= 0 then
      Ada.Text_IO.Put ("Found ");
      Ada.Integer_Text_IO.Put (Target, Width => 0);
      Ada.Text_IO.Put (" at row ");
      Ada.Integer_Text_IO.Put (Found_R, Width => 0);
      Ada.Text_IO.Put (", col ");
      Ada.Integer_Text_IO.Put (Found_C, Width => 0);
      Ada.Text_IO.New_Line;
   else
      Ada.Text_IO.Put_Line ("Not found");
   end if;
end Named_Loops;

The loop name Search is declared with a colon before the loop and repeated after end loop. The exit Search statement breaks out of both the inner and outer loops in one step. Ada has no continue keyword — to skip to the next iteration, you use an if block or goto with a label at the end of the loop body (rarely needed).

Running with Docker

Pull the GNAT image and compile each example with gnatmake:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Pull the official image
docker pull codearchaeology/ada:latest

# Compile and run conditionals.adb
docker run --rm -v $(pwd):/app -w /app codearchaeology/ada:latest \
  sh -c 'gnatmake conditionals.adb && ./conditionals'

# Compile and run case_example.adb
docker run --rm -v $(pwd):/app -w /app codearchaeology/ada:latest \
  sh -c 'gnatmake case_example.adb && ./case_example'

# Compile and run loops.adb
docker run --rm -v $(pwd):/app -w /app codearchaeology/ada:latest \
  sh -c 'gnatmake loops.adb && ./loops'

# Compile and run named_loops.adb
docker run --rm -v $(pwd):/app -w /app codearchaeology/ada:latest \
  sh -c 'gnatmake named_loops.adb && ./named_loops'

Expected Output

Running conditionals:

Score: 87 -> Grade B
Result: Passing

Running case_example:

It's a weekday - time to work
A regular day

Running loops:

Plain loop counting to 3:
1
2
3
Sum 1..5 = 15
Squares from 1 to 4:
1
4
9
16
Counting down:
3
2
1

Running named_loops:

Found 42 at row 2, col 2

Key Concepts

  • if/elsif/else/end if — Ada uses elsif (one word) and always closes with end if. Boolean operators are and, or, not, plus short-circuit and then / or else.
  • case is exhaustive — Every possible value of the selector must be handled, either explicitly or via when others. The compiler enforces this.
  • Three loop forms — Plain loop (infinite, exited explicitly), while ... loop (top-tested), and for I in range loop (counted iteration over a discrete range).
  • for counters are immutable — The loop variable is read-only inside the body, preventing a common class of off-by-one bugs.
  • reverse keyword — Iterate ranges backwards with for I in reverse 1 .. 10 loop.
  • Named loops with exit Name — Label loops to break out of nested iteration cleanly without flags or goto.
  • exit when condition — A concise way to test an exit condition at any point in the loop body.
  • No fall-through, no continuecase branches don’t fall through, and Ada deliberately omits continue to encourage clearer loop structure.

Running Today

All examples can be run using Docker:

docker pull codearchaeology/ada:latest
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining