Fortran procedures
last modified November 14, 2026
A procedure is a named block of statements that a program calls, possibly with
arguments, and Fortran has two kinds: the subroutine, which is
called with call and does not return a value, and the
function, which returns a value and is used inside an expression.
Arguments are listed as ordinary variables and marked with an
intent attribute that says whether the procedure reads or writes
them. This article shows how to write both kinds, where to put them, and how
the compiler checks the calls.
Subroutines
A subroutine begins with subroutine name(arguments) and ends with
end subroutine name. It is called with the call
statement, which passes the arguments in parentheses. In the example below the
subroutine is placed after contains in the main program, which
makes it an internal procedure: it is visible only inside this program and it
can use the program's variables.
program sub1
implicit none
real :: area
call circle_area(2.0, area)
print *, 'area:', area
contains
subroutine circle_area(radius, area)
real, intent(in) :: radius
real, intent(out) :: area
real, parameter :: pi = 4.0 * atan(1.0)
area = pi * radius ** 2
end subroutine circle_area
end program sub1
The program declares one variable, area, and lets the subroutine
fill it. The argument radius is marked intent(in)
because the subroutine only reads it, and area is
intent(out) because the subroutine only assigns it. The constant
pi is local to the subroutine. The call passes 2.0, so the area is
about 12.566.
$ gfortran sub1.f90 -o sub1 $ ./sub1 area: 12.5663710
The intent attribute
intent(in) promises that the procedure will not change the
argument, intent(out) that it will assign it and not read it, and
intent(inout) that it may do both. The attribute is a contract
that the compiler enforces: an assignment to an intent(in)
argument does not compile. It also documents the data flow to the reader,
which is why every dummy argument in modern Fortran carries one.
program swap_demo
implicit none
integer :: a, b
a = 3
b = 8
print *, 'before:', a, b
call swap(a, b)
print *, 'after: ', a, b
contains
subroutine swap(a, b)
integer, intent(inout) :: a, b
integer :: tmp
tmp = a
a = b
b = tmp
end subroutine swap
end program swap_demo
The subroutine exchanges the two arguments, which it can only do because they
are intent(inout). Fortran passes arguments by reference by
default, so the assignments are visible to the caller without any copying or
return value. The local variable tmp holds one of the values
during the exchange, and it disappears when the subroutine returns.
$ gfortran swap.f90 -o swap $ ./swap before: 3 8 after: 8 3
Functions
A function names its result either through the function name itself, declared
with a type before the function keyword, or through a separate
name in a result clause. The second form is clearer in a long
function, because the result has a name of its own and the values assigned in
the body are obvious. A function is called by writing its name with the
arguments in an expression, without call.
program fun1
implicit none
real :: mean, values(5)
values = [4.0, 8.0, 6.0, 5.0, 7.0]
mean = average(values)
print *, 'mean:', mean
contains
function average(x) result(m)
real, intent(in) :: x(:)
real :: m
m = sum(x) / size(x)
end function average
end program fun1
The function takes an assumed-shape array, written x(:), so it
works for an array of any length; size supplies the length inside
the body. The result variable is m, declared as a real, and the
last assignment to it before the return is the value of the call. The mean of
4, 8, 6, 5 and 7 is 6.
$ gfortran fun1.f90 -o fun1 $ ./fun1 mean: 6.00000000
A subroutine with several results
A subroutine can return any number of values through its arguments, which is the usual way to compute a set of statistics in one pass. A function can only return one value, unless that value is an array or a derived type. The following subroutine returns the smallest value, the largest value and the mean of an array.
program mult
implicit none
real :: values(5), lo, hi, avg
values = [4.0, 8.0, 6.0, 5.0, 7.0]
call stats(values, lo, hi, avg)
print *, 'min:', lo
print *, 'max:', hi
print *, 'mean:', avg
contains
subroutine stats(x, lo, hi, avg)
real, intent(in) :: x(:)
real, intent(out) :: lo, hi, avg
lo = minval(x)
hi = maxval(x)
avg = sum(x) / size(x)
end subroutine stats
end program mult
The three intent(out) arguments are filled by the subroutine, and
the array is intent(in). The intrinsic functions
minval, maxval and sum work on the whole
array, so no loop is needed. The minimum is 4, the maximum is 8, and the mean
is again 6. The caller must declare the three output variables, but not their
initial values, because intent(out) makes them undefined on
entry.
$ gfortran mult.f90 -o mult $ ./mult min: 4.00000000 max: 8.00000000 mean: 6.00000000
Host association
An internal procedure sees the variables of the program that contains it, a rule called host association. It can read and write them without passing them as arguments, which is convenient for a small helper and dangerous for a large one, since the data flow is no longer visible in the argument list. A local variable with the same name would hide the host one.
program host
implicit none
integer :: counter
counter = 0
call bump
call bump
call bump
print *, 'counter:', counter
contains
subroutine bump
integer :: step
step = 1
counter = counter + step
end subroutine bump
end program host
The subroutine has no arguments at all: it uses counter from the
host program and declares its own step. Three calls add three to
the counter, so the program prints 3. The variable step is local,
created anew on every call, while counter lives as long as the
program does.
$ gfortran host.f90 -o host $ ./host counter: 3
Keyword arguments
A call may name its arguments instead of relying on their order, as in
call box(width=2.0, height=5.0). The names are the dummy argument
names of the procedure, not the names in the caller, so the call remains
correct when a local variable is renamed. Keyword arguments make a call with
several arguments of the same type readable, and they are the only way to skip
an optional argument in the middle of a list.
program optcheck
implicit none
call box(3.0, 4.0)
call box(3.0)
call box(width=2.0, height=5.0)
contains
subroutine box(height, width)
real, intent(in) :: height
real, intent(in), optional :: width
real :: w
if (present(width)) then
w = width
else
w = height
end if
print *, 'area:', height * w
end subroutine box
end program optcheck
The first call passes both arguments by position and gets an area of 12. The
second omits width, so the subroutine falls back to a square of
side height and prints 9. The third call names its arguments in
the opposite order, which changes nothing, and prints 10. The
optional attribute allows the caller to leave the argument out,
and present tells the procedure whether it was supplied.
$ gfortran optcheck.f90 -o optcheck $ ./optcheck area: 12.0000000 area: 9.00000000 area: 10.0000000
Optional arguments
An optional character argument is often a message that the caller may want to
customize. The procedure must test present before using the
argument, because referencing an absent optional argument is an error. A
common pattern is a small wrapper that supplies a default and calls a second
procedure, but here the test is written directly.
program opt
implicit none
call greet('Ada')
call greet('Ada', '!')
call greet(name='Grace', punctuation='?')
call greet(punctuation='.', name='Alan')
contains
subroutine greet(name, punctuation)
character(len=*), intent(in) :: name
character(len=*), intent(in), optional :: punctuation
if (present(punctuation)) then
print *, 'Hello, ' // trim(name) // punctuation
else
print *, 'Hello, ' // trim(name)
end if
end subroutine greet
end program opt
The four calls differ only in the optional argument. The first omits it and
prints a plain greeting. The other three supply it, by position or by keyword,
and the punctuation marks are appended to the trimmed name. The declared
length len=* takes the length of whatever the caller passes, which
is why the procedure works for names and punctuation of different sizes.
$ gfortran opt.f90 -o opt $ ./opt Hello, Ada Hello, Ada! Hello, Grace? Hello, Alan.
External procedures and interfaces
A procedure may also live outside the program, either in the same file after
the end program or in a separate file compiled together with it.
Such an external procedure has no host, and the caller knows nothing about its
arguments unless the program declares them in an interface block.
The block repeats the procedure's heading and declarations, and the compiler
uses it to check the call.
program ext
implicit none
interface
function square(x) result(y)
real, intent(in) :: x
real :: y
end function square
end interface
print *, square(3.0), square(0.5)
end program ext
function square(x) result(y)
real, intent(in) :: x
real :: y
y = x * x
end function square
The interface block inside the program copies the declaration of
square, so the call is checked against it. The function itself
follows the end of the program in the same file; compiling the file builds both
units, and the linker resolves the call. The squares of 3.0 and 0.5 are 9.0 and
0.25.
$ gfortran ext.f90 -o ext $ ./ext 9.00000000 0.250000000
Why an explicit interface is required
Without an interface, the caller can only guess the types of the arguments
from the call itself. For an assumed-shape argument such as x(:)
the guess is impossible, because the procedure must receive the array bounds
as hidden arguments, and the compiler refuses the call. The error message names
the cause exactly.
program noiface
implicit none
real, external :: total
real :: values(3)
values = [1.0, 2.0, 3.0]
print *, total(values)
end program noiface
function total(x)
real :: x(:)
real :: total
total = sum(x)
end function total
The declaration real, external :: total tells the compiler that
the function returns a real, but nothing about x. Since the
function expects an assumed-shape array, the call cannot be compiled, and the
message asks for an explicit interface. The fix is an interface block like the
one above, or a module, which is the subject of
the next article.
$ gfortran noiface.f90 -o noiface
noiface.f90:3:27:
3 | real, external :: total
| 1
Error: Explicit interface required for ‘total’ at (1): assumed-shape argument
Recursive procedures
A procedure that calls itself must be declared recursive, because
the compiler otherwise assumes that no recursion occurs and may keep local
variables in static storage. With the keyword, every call gets its own copy of
the local variables. A recursive function needs a base case that returns
without calling itself, or the recursion never ends.
program fact
implicit none
integer :: n
do n = 0, 10, 3
print *, n, factorial(n)
end do
contains
recursive function factorial(n) result(f)
integer, intent(in) :: n
integer :: f
if (n <= 1) then
f = 1
else
f = n * factorial(n - 1)
end if
end function factorial
end program fact
The base case is n <= 1, which returns 1 without another call.
Every other value multiplies n by the factorial of one less, so
the function unwinds through all the values down to 1. The loop calls it with
0, 3, 6 and 9, and the results are 1, 6, 720 and 362880.
$ gfortran fact.f90 -o fact
$ ./fact
0 1
3 6
6 720
9 362880
A complete example
The program below solves a quadratic equation with a subroutine. The
coefficients are intent(in), the roots and the success flag are
intent(out), and the subroutine computes the discriminant and
branches on its sign. The caller checks the flag before printing the roots.
program quadratic
implicit none
real :: a, b, c, x1, x2
logical :: ok
a = 1.0
b = -3.0
c = 2.0
call solve(a, b, c, x1, x2, ok)
if (ok) then
print '(a, f0.2, a, f0.2)', 'roots: ', x1, ' and ', x2
else
print *, 'no real roots'
end if
contains
subroutine solve(a, b, c, x1, x2, ok)
real, intent(in) :: a, b, c
real, intent(out) :: x1, x2
logical, intent(out) :: ok
real :: d, q
d = b * b - 4.0 * a * c
ok = d >= 0.0
if (ok) then
q = sqrt(d)
x1 = (-b + q) / (2.0 * a)
x2 = (-b - q) / (2.0 * a)
else
x1 = 0.0
x2 = 0.0
end if
end subroutine solve
end program quadratic
The equation is x**2 - 3x + 2 = 0, whose discriminant is 1, so the
roots are 2 and 1. The subroutine sets both output roots only in the branch
where they exist, and the caller prints them with two decimals. The
f0.2 descriptor takes the width from the value and keeps two
digits after the point, and the literals between the numbers supply the text.
$ gfortran quad.f90 -o quad $ ./quad roots: 2.00 and 1.00
Next steps
A later article on Fortran modules shows how to put procedures in a separate compilation unit that other programs can use, which is the standard way to give every caller an explicit interface. The article on Fortran arrays describes the assumed-shape arguments used above in full, and the article on recursion takes the recursive procedures further.
Source
GNU Fortran: functions and subroutines
GNU Fortran: the INTENT attribute
Fortran 2018 standard (J3/18-007r1)
In this article we have written subroutines and functions, marked arguments
with intent, returned values through a result
variable, used host association, called procedures with keyword and optional
arguments, declared an external procedure in an interface block, seen why an
explicit interface is required, and written a recursive function. The previous
article, Fortran intrinsic numeric
functions, describes the built-in functions these procedures call.
Author
List all Fortran tutorials.