ZetCode

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.

sub1.f90
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.

swap.f90
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.

fun1.f90
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.

mult.f90
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.

host.f90
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.

optcheck.f90
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.

opt.f90
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.

ext.f90
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.

noiface.f90
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.

fact.f90
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.

quad.f90
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

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Fortran tutorials.