Control Flow
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Fortran 2023 (ISO/IEC 1539-1:2023, J3/23-007r1) and was checked on 2026-10-18. Every
example was compiled warning-free with gfortran -std=f2023 -Wall -Wextra -pedantic -fimplicit-none -fcheck=all
(GNU Fortran 16.2) and run, and most also with LLVM Flang 23; the output shown is real output. Examples that are
deliberately wrong or that deliberately trigger a diagnostic are labelled as such. Execution control is defined in clause 11
of the standard ("Execution control"): 11.1 covers the constructs that contain blocks, 11.2 branching, 11.3 continue and
11.4 stop and error stop.
Fortran has no braces and no indentation rules: every block statement is closed by an explicit end statement
(end if, end do, end select), which makes the structure unambiguous and lets you repeat the construct name for
checking. There is no while or for keyword; both are spellings of do. The boolean operators are in
Expressions and Assignment; procedures, which also
transfer control, in Procedures, Functions and Subroutines.
Readers coming from C will find the corresponding statements in
C Control Flow and from Rust in
Rust Control Flow; the differences that matter are called out below.
| Construct | Purpose | Standard clause (J3/23-007r1) |
|---|---|---|
|
two-way and multi-way branching on logical expressions |
11.1.8 |
|
multi-way branching on an integer, character or logical value |
11.1.9 |
|
iteration; |
11.1.7, 11.1.12 |
|
executable region with its own declarations |
11.1.4 |
|
temporary names for expressions and long designators |
11.1.3 |
|
branch on the dynamic type of a polymorphic object |
11.1.11 |
|
branch on the rank of an assumed-rank dummy |
11.1.10 |
|
terminate execution |
11.4 |
|
unstructured branching (legacy) |
11.2, 11.3 |
The if construct and the if statement
An if construct (11.1.8) has a then after the condition, any number of else if (...) then branches, an optional
else, and an end if. The conditions are scalar logical expressions; there is no implicit conversion from integers
or reals, so if (n) is a compile-time error. The if statement is the single-line form: a condition followed by one
action statement, with no then and no end if. The action cannot be another if statement or a block-opening statement.
An if construct may carry a name, and when it does the name must be repeated on end if and on every else if and
else keyword of that construct (else check).
The expression in an else if is evaluated only when every earlier condition was false. Within one expression, however,
the standard does not require short-circuit evaluation: a .and. b may evaluate b even when a is false, and a processor
may evaluate mathematically equivalent forms. The last statement of the example below is therefore guarded only by luck;
the safe pattern is a nested if or, since Fortran 2023, a conditional expression (see below).
module heatflow_kinds
use, intrinsic :: iso_fortran_env, only: real64
implicit none
private
public :: dp
integer, parameter :: dp = real64
end module heatflow_kinds
program if_demo
use heatflow_kinds, only: dp
implicit none
real(dp) :: residual, tolerance
integer :: step, max_steps
character(len=*), parameter :: fmt = '(a, i0, a, es9.2)'
tolerance = 1.0e-3_dp
max_steps = 2000
residual = 5.0e-4_dp
step = 120
! if construct with else if / else
if (residual < tolerance) then
print fmt, 'converged at step ', step, ', residual ', residual
else if (step >= max_steps) then
print '(a)', 'step budget exhausted'
else
print '(a)', 'keep iterating'
end if
! if statement: one action statement, no then / end if
if (step > max_steps) stop 1
! named if construct; the name must be repeated on every branch keyword
check: if (residual < 0.0_dp) then
print '(a)', 'impossible: a residual is a norm'
else check
print '(a)', 'residual is non-negative'
end if check
! .and. / .or. do not short circuit: both operands may be evaluated
if (step >= 1 .and. residual / real(step, dp) < tolerance) print '(a)', 'ratio below tolerance'
end program if_demo
Output:
converged at step 120, residual 5.00E-04
residual is non-negative
ratio below tolerance
select case
select case (selector) (11.1.9) compares one scalar value against lists of constant values. The selector must be
integer, character or logical; real selectors are not allowed. Each case takes a parenthesised list of values or
ranges; a range is low:high, :high (everything up to and including high) or low: (everything from low). The case
values must be constant expressions of the same type as the selector (for character, the kind must match, the length
may differ), and the lists of all cases must not overlap. Exactly zero or one case is selected; there is no fall through
and no break, so each case block ends where the next case begins. case default handles every remaining value and
may appear anywhere, although putting it last reads best. Without a case default, an unmatched selector value simply
skips the whole construct, so a function that assigns its result inside the cases would return an undefined result;
grade therefore ends with a default for the lengths between its listed values.
character selectors are compared as if the shorter operand were padded with blanks, so 'go ' matches 'go'.
Ranges use the collating sequence of the character kind. Because enum, bind(c) enumerators (see
Enumerations) are named integer constants, they work directly
as case values, as bc_names does for the heatflow boundary conditions. The F2023 enumeration type statement is not accepted
by GNU Fortran 16.2 or, by default, LLVM Flang 23, so it is not used in the examples.
module bc_names
implicit none
private
public :: dirichlet, neumann, periodic, describe, grade, command_kind
enum, bind(c)
enumerator :: dirichlet = 1, neumann = 2, periodic = 3
end enum
contains
function describe(bc) result(text)
integer, intent(in) :: bc
character(len=:), allocatable :: text
select case (bc)
case (dirichlet)
text = 'fixed temperature'
case (neumann)
text = 'fixed flux'
case (periodic)
text = 'wrap around'
case default
text = 'unknown'
end select
end function describe
! ranges: low:high, :high and low: are all allowed for integer, character and logical selectors
function grade(steps) result(text)
integer, intent(in) :: steps
character(len=:), allocatable :: text
select case (steps)
case (:0)
text = 'nothing to do'
case (1:99)
text = 'short run'
case (100, 200, 300:999)
text = 'medium run'
case (1000:)
text = 'long run'
case default
text = 'odd length'
end select
end function grade
! character selector: compared as if the shorter value were padded with blanks
function command_kind(word) result(kind)
character(len=*), intent(in) :: word
integer :: kind
select case (word)
case ('run', 'go')
kind = 1
case ('a':'f')
kind = 2
case default
kind = 0
end select
end function command_kind
end module bc_names
program select_demo
use bc_names
implicit none
integer :: i
integer, parameter :: probes(5) = [-3, 5, 150, 200, 4000]
do i = 1, 3
print '(i0, 2a)', i, ': ', describe(i)
end do
print '(2a)', '9: ', describe(9)
do i = 1, size(probes)
print '(i0, 2a)', probes(i), ' -> ', grade(probes(i))
end do
print '(*(i0, 1x))', command_kind('run'), command_kind('go '), command_kind('cat'), command_kind('zeta')
end program select_demo
Output:
1: fixed temperature
2: fixed flux
3: wrap around
9: unknown
-3 -> nothing to do
5 -> short run
150 -> odd length
200 -> medium run
4000 -> long run
1 1 2 0
The overlap rule is checked at compile time. Replacing 'a':'f' by 'a':'m' in command_kind is rejected by
gfortran, because 'go' falls inside that range (this fragment was compiled to obtain the message, it is not one of the
runnable examples above):
Error: CASE label at (1) overlaps with CASE label at (2)
The do construct
All iteration is do (11.1.7), in three spellings.
-
Counted —
do i = first, last [, step]. The three expressions are evaluated once, before the loop starts. The number of iterations ismax(0, (last - first + step) / step)using integer division, and it is fixed at that point; changing a variable that appears inlastorstepinside the body does not change the count.stepdefaults to 1 and must not be zero. -
do while (condition)— the condition is tested before every pass, including the first. -
Unbounded —
dowith no control; it runs until anexit, areturn, astop, or a branch out. Use it for loops that test their condition in the middle or at the end (a "repeat until" loop).
The do variable is an ordinary scalar integer variable that must not be redefined by the body; assigning it is a
compile-time error (see the error example below). Fortran has no loop-local scope for it, so the variable survives the
loop and holds the value the standard defines: first + iterations * step, which is one step beyond the last executed
value (13 in the example, not 10). After a zero-trip loop it holds first, because it is assigned before the trip count is
tested. Real and double precision do variables were deleted from the language long ago (Fortran 95), so the book-era
idiom do x = 0.0, 1.0, 0.1 no longer compiles with -std=f2023; loop over an integer index and compute the real value.
exit leaves the innermost do (or, with a name, the named one); cycle abandons the current pass and goes to the next
iteration test. Both apply only to do constructs, apart from the named exit described next.
program do_demo
implicit none
integer :: i, n, count_down, lo, hi
real :: total
! counted loop: iteration count = max(0, (end - start + step) / step), fixed before the first pass
do i = 1, 10, 3
write (*, '(i0, 1x)', advance='no') i
end do
print '(a, i0)', ' | i after the loop = ', i
! zero-trip loop: the body never runs and i is still assigned the start value
lo = 5; hi = 1
do i = lo, hi
print '(a)', 'never printed'
end do
print '(a, i0)', 'i after a zero-trip loop = ', i
! decreasing loop needs a negative stride
do count_down = 3, 1, -1
write (*, '(i0, 1x)', advance='no') count_down
end do
print '(a)', 'liftoff'
! the end expression is evaluated once: changing n inside has no effect on the trip count
n = 3
total = 0.0
do i = 1, n
n = 100
total = total + 1.0
end do
print '(a, f4.1, a, i0)', 'iterations = ', total, ', n = ', n
! do while and unbounded do with exit / cycle
n = 1
do while (n < 50)
n = n * 2
end do
print '(a, i0)', 'first power of two >= 50 = ', n
n = 0
do
n = n + 1
if (mod(n, 2) == 0) cycle
if (n > 7) exit
write (*, '(i0, 1x)', advance='no') n
end do
print '(a, i0)', '| left with n = ', n
end program do_demo
Output:
1 4 7 10 | i after the loop = 13
i after a zero-trip loop = 5
3 2 1 liftoff
iterations = 3.0, n = 100
first power of two >= 50 = 64
1 3 5 7 | left with n = 9
Compiled with -Wall the zero-trip case warns about a loop that "will be executed zero times" if both bounds are literal
constants (-Wzerotrip, enabled by -Wall); the example uses variables so the demonstration is warning-free.
Source: J3/23-007r1, 11.1.7 DO construct
Construct names, exit and cycle
Every if, select case, do, block, associate, select type, select rank, critical and change team construct
can be given a name: name: do ... … end do name. The name is an identifier in the construct’s scoping unit and
must be repeated on the closing statement (and, for if, on else if and else; for select case, an optional matching name
may follow each case selector). Names let exit and cycle address an
outer loop, which replaces the usual go to or flag variable for breaking out of nested loops.
Since Fortran 2008, exit construct-name is not limited to do: it can leave a named block, if, select case
or other construct (11.1.12). This is the structured replacement for jumping to a label at the end of a region.
cycle still needs a do construct.
program named_exit_demo
use, intrinsic :: iso_fortran_env, only: real64
implicit none
integer, parameter :: dp = real64
integer, parameter :: nx = 4, ny = 3
real(dp) :: u(nx, ny)
integer :: i, j
u = 0.0_dp
u(3, 2) = -1.0_dp ! a nonphysical negative temperature
! exit and cycle accept a construct name: leave / continue the outer loop from the inner one
scan: do j = 1, ny
row: do i = 1, nx
if (abs(u(i, j)) <= 0.0_dp) cycle row
print '(a, i0, a, i0, a)', 'nonzero at (', i, ', ', j, ')'
exit scan
end do row
end do scan
print '(a, 2(i0, 1x))', 'stopped at i, j = ', i, j
! F2008: exit works on any named construct, not only on do
validate: block
if (any(u < 0.0_dp)) exit validate
print '(a)', 'all temperatures valid' ! skipped here
end block validate
pick: select case (nx)
case (4)
if (ny == 3) exit pick
print '(a)', 'not reached'
case default
print '(a)', 'not reached either'
end select pick
check: if (nx > 0) then
if (any(u < 0.0_dp)) exit check
print '(a)', 'also skipped'
end if check
print '(a)', 'done'
end program named_exit_demo
Output:
nonzero at (3, 2)
stopped at i, j = 3 2
done
An unnamed exit or cycle must lie inside a do. The next two programs are deliberately non-conforming and were
compiled only to capture the diagnostics.
! INTENTIONALLY NON-CONFORMING: the do variable is redefined inside its own loop.
program bad_do_variable
implicit none
integer :: i
do i = 1, 3
i = i + 1
end do
end program bad_do_variable
09a_bad_do_variable.f90:6:13:
5 | do i = 1, 3
| 2
6 | i = i + 1
| 1
Error: Variable 'i' at (1) cannot be redefined inside loop beginning at (2)
! INTENTIONALLY NON-CONFORMING: exit and cycle outside the constructs they need.
program bad_exit
implicit none
integer :: i
i = 1
if (i > 3) exit
cycle
end program bad_exit
09b_bad_exit.f90:6:17:
6 | if (i > 3) exit
| 1
Error: EXIT statement at (1) is not within a construct
09b_bad_exit.f90:7:7:
7 | cycle
| 1
Error: CYCLE statement at (1) is not within a construct
block and associate
block (11.1.4) opens an executable region that may begin with specification statements. Variables declared inside are
local to it, created when the block is entered and gone at end block, including automatic arrays whose bounds depend on
runtime values. Use it to keep a temporary next to its single use, to give exit something to leave, or to declare
allocatable variables that are deallocated automatically on exit. The specification part of a block has restrictions (for example it cannot contain an implicit statement), and a branch from
outside into a block is not allowed.
associate (11.1.3) gives names to expressions or designators for the duration of the construct. If the selector is a
variable (g%u, g%u(3, 2)), the associate-name is another name for that variable and may be assigned to; if it is an
expression (sum(g%u)), the name is read-only. The associate-name takes its type, kind, rank and shape from the selector, and
inherits its target, asynchronous and volatile attributes. It is the Fortran replacement for the classic
pointer alias (p => g%u(3, 2)): there is no pointer involved, nothing can dangle, the compiler knows
nothing else is changed through it, and the name disappears at end associate. The selector is evaluated once, on entry.
associate is also what makes long derived-type chains readable in numeric kernels.
module heatflow_kinds
use, intrinsic :: iso_fortran_env, only: real64
implicit none
private
public :: dp
integer, parameter :: dp = real64
end module heatflow_kinds
module grid_mod
use heatflow_kinds, only: dp
implicit none
private
public :: grid_t
type :: grid_t
integer :: nx = 0, ny = 0
real(dp) :: dx = 0, dy = 0
real(dp), allocatable :: u(:, :)
end type grid_t
end module grid_mod
program block_associate_demo
use heatflow_kinds, only: dp
use grid_mod, only: grid_t
implicit none
type(grid_t) :: g
integer :: i
g%nx = 5; g%ny = 4; g%dx = 0.25_dp; g%dy = 0.5_dp
allocate (g%u(g%nx, g%ny), source=20.0_dp)
g%u(3, 2) = 100.0_dp
! associate: names for expressions or long designators, valid only inside the construct
associate (u => g%u, hx => g%dx, hy => g%dy, centre => g%u(3, 2))
print '(a, f6.3)', 'dx*dy = ', hx * hy
print '(a, f6.1)', 'centre = ', centre
centre = 90.0_dp ! a variable selector is definable through its associate-name
u(1, 1) = 0.0_dp
end associate
print '(a, 2f6.1)', 'modified via associate: ', g%u(3, 2), g%u(1, 1)
! an expression selector is an expression value: it cannot be assigned to
associate (total => sum(g%u))
print '(a, f8.1)', 'sum = ', total
end associate
! block: local declarations with a limited scope
do i = 1, 2
block
real(dp) :: scratch(g%nx) ! automatic, sized when the block is entered
integer :: k
scratch = [(real(k, dp) * real(i, dp), k = 1, g%nx)]
print '(a, i0, a, f5.1)', 'pass ', i, ': sum(scratch) = ', sum(scratch)
end block
end do
end program block_associate_demo
Output:
dx*dy = 0.125
centre = 100.0
modified via associate: 90.0 0.0
sum = 450.0
pass 1: sum(scratch) = 15.0
pass 2: sum(scratch) = 30.0
select type and select rank (preview)
select type (11.1.11) tests the dynamic type of a class(...) selector and gives each branch a correctly typed view.
type is (t) matches the exact type, class is (t) matches t or any extension (the most specific such branch wins),
and class default catches the rest. Inside a type is branch the associate-name is not polymorphic and the
components of that type are accessible; the construct needs the selector to be class(...). select type belongs to the
object-oriented material, which is developed in
Object Oriented Programming and
Type-Bound Procedures, Abstract Types and Finalization.
select rank (11.1.10, Fortran 2018) does the same for the rank of an assumed-rank dummy argument declared with
dimension(..). The branches are rank (n), rank (*) for an assumed-size array, and rank default. Assumed-rank
and select rank are used mainly to write one wrapper for every rank, especially for C interoperability; see
Array Arguments: Assumed Shape, Size and Rank
and Generic and Rank Independent Programming.
module shapes
use, intrinsic :: iso_fortran_env, only: real64
implicit none
private
public :: solver_t, jacobi_t, ftcs_t, name_of, rank_of
type, abstract :: solver_t
integer :: steps = 0
end type solver_t
type, extends(solver_t) :: jacobi_t
end type jacobi_t
type, extends(solver_t) :: ftcs_t
real(real64) :: alpha = 1.0_real64
end type ftcs_t
contains
function name_of(s) result(text)
class(solver_t), intent(in) :: s
character(len=:), allocatable :: text
select type (s)
type is (jacobi_t)
text = 'jacobi'
class is (ftcs_t)
text = 'ftcs, alpha=' // trim(adjustl(real_text(s%alpha)))
class default
text = 'unknown solver'
end select
end function name_of
function real_text(x) result(t)
real(real64), intent(in) :: x
character(len=16) :: t
write (t, '(f4.2)') x
end function real_text
function rank_of(a) result(text)
real(real64), intent(in) :: a(..)
character(len=:), allocatable :: text
select rank (a)
rank (0)
text = 'scalar'
rank (1)
text = 'vector of ' // trim(adjustl(int_text(size(a))))
rank (2)
text = 'matrix ' // trim(adjustl(int_text(size(a, 1)))) // 'x' // trim(adjustl(int_text(size(a, 2))))
rank default
text = 'rank > 2'
end select
end function rank_of
function int_text(n) result(t)
integer, intent(in) :: n
character(len=12) :: t
write (t, '(i0)') n
end function int_text
end module shapes
program select_type_demo
use, intrinsic :: iso_fortran_env, only: real64
use shapes
implicit none
class(solver_t), allocatable :: s
real(real64) :: m(3, 2) = 0.0_real64, v(4) = 0.0_real64
allocate (jacobi_t :: s)
print '(2a)', 'solver: ', name_of(s)
deallocate (s)
allocate (ftcs_t :: s)
select type (s)
type is (ftcs_t)
s%alpha = 0.25_real64
end select
print '(2a)', 'solver: ', name_of(s)
print '(*(a, :, "; "))', rank_of(1.0_real64), rank_of(v), rank_of(m)
end program select_type_demo
Output:
solver: jacobi
solver: ftcs, alpha=0.25
scalar; vector of 4; matrix 3x2
Conditional expressions (Fortran 2023)
Fortran 2023 adds the conditional expression ( condition ? value-if-true : value-if-false ). Only the selected branch is evaluated, so it gives a standard way to write a guarded division or a
guarded array access in a single expression, something .and. alone cannot promise. The surrounding parentheses are
part of the syntax. GNU Fortran 16.2 and LLVM Flang 23 both accepted the example below (gfortran in strict -std=f2023 mode);
other compilers may not support it yet, see
Compilers and Compiler Options. The syntax and
the operator rules are described on
Expressions and Assignment.
program cond_expr
use, intrinsic :: iso_fortran_env, only: real64
implicit none
integer, parameter :: dp = real64
real(dp) :: residual, rate
integer :: step
residual = 2.0e-3_dp
do step = 0, 2
! only the selected branch is evaluated, so step = 0 never divides by zero
rate = (step > 0 ? residual / real(step, dp) : 0.0_dp)
print '(a, i0, a, es9.2)', 'step ', step, ': rate = ', rate
end do
end program cond_expr
Output:
step 0: rate = 0.00E+00
step 1: rate = 2.00E-03
step 2: rate = 1.00E-03
stop and error stop
stop (11.4) ends the program normally from anywhere, including from inside a procedure; error stop ends it as an error
termination. Both accept an optional stop code, either a scalar integer or a scalar default-character expression.
Without a code stop yields exit status 0 and prints nothing, and error stop yields a non-zero status. A character code
is written to the error unit; an integer code is written as STOP n and, on the tested platform, becomes the process exit
status. The meaning of the exit status is processor-dependent. Fortran 2018 added the optional quiet= specifier (a scalar
logical): when true, the processor does not print the stop code or the error-termination report, but the termination is
still an error stop. error stop may also appear in a pure procedure, which stop may not. In a program that
uses images, stop stops that image normally and error stop makes all images terminate; see
Error Handling and Program Termination
and Coarrays, Images and Synchronization.
program stop_demo
implicit none
integer :: mode
character(len=8) :: arg
mode = 0
if (command_argument_count() >= 1) then
call get_command_argument(1, arg)
read (arg, *) mode
end if
select case (mode)
case (0)
print '(a)', 'normal end'
case (1)
stop ! exit status 0, no message
case (2)
stop 'finished early' ! message to stderr, exit status 0
case (3)
stop 7 ! stop code is also the exit status on most systems
case (4)
error stop 'solver diverged' ! exit status non-zero, error termination
case (5)
error stop 3, quiet=.true. ! F2018: quiet= suppresses the termination report
end select
end program stop_demo
Running it with the mode as a command-line argument, compiled with -fno-backtrace to keep the report short, gave:
$ ./stop_demo 0
normal end
exit status 0
$ ./stop_demo 1
exit status 0
$ ./stop_demo 2
STOP finished early
exit status 0
$ ./stop_demo 3
STOP 7
exit status 7
$ ./stop_demo 4
ERROR STOP solver diverged
exit status 1
$ ./stop_demo 5
exit status 3
By default gfortran also prints a backtrace after Error termination. for error stop; -fno-backtrace removes it and the
exit status is the same.
continue and go to: recognition only
Old code expresses control flow with labels. continue (11.3) does nothing and exists to carry a label. go to label (11.2.2)
branches unconditionally; the target must be an executable statement in the same scoping unit, and a branch into
a construct from outside is not allowed. The computed go to (l1, l2, ...) expr (11.2.3) selects a label by an integer
and is obsolescent: gfortran warns about it, as the compile output below shows, and select case replaces it. The
arithmetic if (if (x) 10, 20, 30) and the nonblock do, in which several loops share a labelled terminal statement or end
on a statement other than end do or continue, were deleted in Fortran 2018 and no longer compile under -std=f2023; both are
covered, with their rewrites, on
Legacy Features and Modernization. New code should
use do while, exit, cycle and select case, which compile to the same jumps but state intent.
The example compiles in Fortran 2023 mode, with the one warning that the labelled form earns; that warning is expected and shown below.
! Recognition only: legacy-style control flow that still compiles in Fortran 2023.
program legacy_flow
implicit none
integer :: i, total
integer :: choice
total = 0
i = 0
10 continue ! labelled continue: a branch target
i = i + 1
total = total + i
if (i < 5) go to 10 ! a backward go to is a hand-made loop
print '(a, i0)', 'total = ', total
choice = 2
go to (20, 30) choice ! computed go to: obsolescent
20 print '(a)', 'label 20'
go to 40
30 print '(a)', 'label 30'
40 continue
end program legacy_flow
Compiler output (a warning, not an error) and run output:
08_goto.f90:16:58:
16 | go to (20, 30) choice ! computed go to: obsolescent
| 1
Warning: Obsolescent feature: Computed GOTO at (1)
total = 15
label 30
Where the books are outdated
The books on the reference list predate Fortran 2023 and do not describe the conditional expression, which is now a
standard way to guard evaluation. Their control-flow examples may also use constructs that the standard has since removed
or that GNU Fortran flags with -std=f2023: real do variables, shared or nonblock do terminators and the arithmetic if. Use
the structured forms shown here instead (integer do variable, end do, select case). Where a book states that
exit and cycle apply only to do, remember that exit may name any construct since Fortran 2008. Statements such
as pause and assigned go to are gone entirely (Fortran 95).
References
-
J3/23-007r1: Fortran 2023 final draft, clause 11 "Execution control"
-
GNU Fortran: Options to request or suppress errors and warnings (
-Wzerotrip,-Wcompare-reals,-std=)