ZetCode

Fortran style guide

last modified November 14, 2026

A style guide is a set of decisions made once so that they do not have to be made again in every routine. The compiler accepts many programs that a reader cannot follow, and Fortran in particular carries a century of habits from an era when a variable name cost memory; most of them are now only a source of bugs. The rules below are the ones this series follows, each with the mistake it prevents.

This article shows the difference between names chosen for the compiler and names chosen for a reader, what happens to a misspelled variable without implicit none and with it, why every dummy argument should carry an intent, what a kind does for a sum of a million numbers, how a module's private list protects its internals, and why a line has to be broken at a sensible place. It ends with a checklist for a new file.

Why a style guide

Code is read far more often than it is written, and a large part of the reading is done by the person who wrote it, months later. A style guide pays for itself in three ways: it makes names mean something, it lets the compiler catch the mistakes that a reader would otherwise have to find, and it removes the small decisions that waste attention. The rules here are not about taste in indentation; each one is tied to a class of defect.

The examples below come in pairs, and every pair produces the same output. That is the point: the good version is not a different program, it is the same program with the accidents removed. The compile lines and the results are shown so that the claims can be checked.

Names that say what they hold

A name is documentation that the compiler checks. Single letters are fine for a loop index out of three lines of arithmetic, and poor for anything that lives longer: a, s and m in the first program require the reader to reconstruct that they are an array, a sum and a mean. The second program spends a few more characters and removes that work.

names_bad.f90
program calc
    implicit none
    real :: a(5), s, m
    integer :: i

    do i = 1, 5
        a(i) = real(i) * 1.5
    end do
    s = 0.0
    do i = 1, 5
        s = s + a(i)
    end do
    m = s / 5.0
    print '(a, f0.2)', 'the mean is ', m
end program calc

The program is short enough that the names can be decoded in a minute, which is exactly why this style survives: it is tolerable in a ten-line example and unreadable in a five-hundred-line routine. The literal 5.0 in the division is the same kind of problem, because it repeats the bound of the array in a second place.

names_good.f90
program average_demo
    implicit none
    integer, parameter :: count = 5
    real, parameter :: step = 1.5
    real :: values(count), total, average
    integer :: i

    do i = 1, count
        values(i) = real(i) * step
    end do
    total = 0.0
    do i = 1, count
        total = total + values(i)
    end do
    average = total / real(count)
    print '(a, f0.2)', 'the mean is ', average
end program average_demo

The good version names the count and the step as parameters, so the array bound and the divisor come from one place, and the three working variables say what they hold. Both programs print the same mean of 4.50, which is the control that the rewrite changed nothing but the reading.

$ gfortran -std=f2018 -Wall -Wextra -fcheck=all names_bad.f90 -o names_bad
$ ./names_bad
the mean is 4.50
$ gfortran -std=f2018 -Wall -Wextra -fcheck=all names_good.f90 -o names_good
$ ./names_good
the mean is 4.50

implicit none in every program unit

Without implicit none, a name that has never been declared gets a type from its first letter: I to N is integer and the rest is real. That turns a misspelling into a new variable instead of an error. The program below declares nothing. The counter is a real because its name starts with c, and the author meant to add one to total but typed totl, so the intended variable keeps its value and a new one takes the increment.

typo_bad.f90
program typo_bad
    count = 10
    total = 10.0
    totl = total + 1.0
    print *, 'count is ', count
    print *, 'total is ', total
    print *, 'totl is  ', totl
end program typo_bad

The list-directed output shows the type of each name without any extra work: count prints as a real with seven decimals, total is still 10.0 and the stray totl holds 11.0. Nothing failed, and the increment went to a variable the program never meant to have. Declaring the two names and adding implicit none turns the same line into a compile error.

typo_none.f90
program typo_none
    implicit none
    integer :: count
    real :: total

    count = 10
    total = 10.0
    totl = total + 1.0
    print *, 'count is ', count, ' total is ', total
end program typo_none

The compiler suggests the intended name, which is the fastest possible repair. The corrected program declares the counter as an integer and the total as a real, and adds one to the total itself.

typo_ok.f90
program typo_ok
    implicit none
    integer :: count
    real :: total

    count = 10
    total = 10.0
    total = total + 1.0
    print '(a, i0)', 'count is ', count
    print '(a, f0.1)', 'total is ', total
end program typo_ok

The first run prints a real counter, a total that never changed and a stray variable, the second refuses to compile, and the third prints 10 and 11.0. implicit none is the single most valuable line in a Fortran program, and it costs one line per program unit.

$ gfortran -std=f2018 typo_bad.f90 -o typo_bad
$ ./typo_bad
 count is    10.0000000    
 total is    10.0000000    
 totl is     11.0000000    
$ gfortran -std=f2018 typo_none.f90 -o typo_none
typo_none.f90:8:8:

    8 |     totl = total + 1.0
      |        1
Error: Symbol ‘totl’ at (1) has no IMPLICIT type; did you mean ‘total’?
$ gfortran -std=f2018 -Wall -Wextra -fcheck=all typo_ok.f90 -o typo_ok
$ ./typo_ok
count is 10
total is 11.0

intent on every dummy argument

intent(in) promises that a routine does not change its argument, intent(out) that it defines it, and intent(inout) that it does both. The declaration is documentation, an optimisation hint and a check: an assignment to an intent(in) dummy is a compile error, and the caller knows which of its variables may change. The subroutine below claims to read its array and then writes to it.

intent_bad.f90
module scaling
    implicit none
contains
    subroutine double_all(values)
        real, intent(in) :: values(:)
        integer :: i

        do i = 1, size(values)
            values(i) = 2.0 * values(i)
        end do
    end subroutine double_all
end module scaling

program intent_bad
    use scaling
    implicit none
    real :: x(3)

    x = [1.0, 2.0, 3.0]
    call double_all(x)
    print *, x
end program intent_bad

The compiler points at the assignment and names the declaration that forbids it. The corrected subroutine declares intent(inout), which is the truth about what it does, and the program that calls it prints the doubled array.

intent_good.f90
module scaling
    implicit none
    private
    public :: double_all
contains
    subroutine double_all(values)
        real, intent(inout) :: values(:)
        integer :: i

        do i = 1, size(values)
            values(i) = 2.0 * values(i)
        end do
    end subroutine double_all
end module scaling

program intent_good
    use scaling
    implicit none
    real :: x(3)

    x = [1.0, 2.0, 3.0]
    call double_all(x)
    print '(a, 3f5.1)', 'the doubled array: ', x
end program intent_good

The module also marks double_all public and everything else private, so the only thing a caller can name is the subroutine. Both the intent and the visibility list are contracts that the compiler enforces on the author's behalf.

$ gfortran -std=f2018 -Wall -Wextra intent_bad.f90 -o intent_bad
intent_bad.f90:9:12:

    9 |             values(i) = 2.0 * values(i)
      |            1
Error: Dummy argument ‘values’ with INTENT(IN) in variable definition context (assignment) at (1)
intent_bad.f90:15:9:

   15 |     use scaling
      |         1
Fatal Error: Cannot open module file ‘scaling.mod’ for reading at (1): No such file or directory
compilation terminated.
$ gfortran -std=f2018 -Wall -Wextra -fcheck=all intent_good.f90 -o intent_good
$ ./intent_good
the doubled array:   2.0  4.0  6.0

Kinds and literals

A bare real is whatever the default kind is, which on this compiler is real32, about seven decimal digits. A sum of a million numbers in that kind accumulates a rounding error much larger than the answer's last digit, and the fix is to declare real(real64) and to write the literals with the matching suffix. The program below performs the same addition in both kinds and compares the results with the exact value.

kinds.f90
program kinds
    use iso_fortran_env, only: real32, real64
    implicit none
    integer, parameter :: n = 1000000
    integer :: i
    real(real32) :: single
    real(real64) :: double, exact

    single = 0.0_real32
    double = 0.0_real64
    do i = 1, n
        single = single + 0.1_real32
        double = double + 0.1_real64
    end do
    exact = 0.1_real64 * real(n, real64)

    print '(a, es12.4)', 'epsilon of real32: ', epsilon(1.0_real32)
    print '(a, es12.4)', 'epsilon of real64: ', epsilon(1.0_real64)
    print '(a, f12.4)', 'the single sum:    ', single
    print '(a, f12.4)', 'the double sum:    ', double
    print '(a, f12.4)', 'the exact value:   ', exact
    print '(a, es12.4)', 'the single error:  ', abs(single - exact)
    print '(a, es12.4)', 'the double error:  ', abs(double - exact)
    print '(a, l1)', 'a default real is real32: ', kind(1.0) == real32
end program kinds

One tenth is not representable in binary, so every addition rounds; after a million of them the single-precision sum is off by 958, about one percent, while the double sum is off by 1.3e-6. The epsilon values differ by nine orders of magnitude and explain the gap. The literal suffixes 0.1_real32 and 0.1_real64 matter as much as the declarations: a suffix-free 0.1 would be converted from the default kind and lose the extra digits before the first addition.

$ gfortran -std=f2018 -Wall -Wextra -fcheck=all kinds.f90 -o kinds
$ ./kinds
epsilon of real32:   1.1921E-07
epsilon of real64:   2.2204E-16
the single sum:     100958.3438
the double sum:     100000.0000
the exact value:    100000.0000
the single error:    9.5834E+02
the double error:    1.3329E-06
a default real is real32: T

Modules with a private surface

A module should export the procedures that form its interface and hide everything else. private as the default, followed by an explicit public list, means that a helper can be renamed or deleted without touching a caller, and that no caller can depend on an accident. The module below exports one function and keeps the squaring helper to itself.

shapes.f90
module shapes
    use iso_fortran_env, only: real64
    implicit none
    private
    public :: circle_area

    real(real64), parameter :: pi = 3.141592653589793_real64
contains
    pure real(real64) function circle_area(radius) result(area)
        real(real64), intent(in) :: radius

        area = pi * square(radius)
    end function circle_area

    pure real(real64) function square(x) result(y)
        real(real64), intent(in) :: x

        y = x * x
    end function square
end module shapes

The program that uses the module sees only circle_area, and the compiler reads the interface from shapes.mod rather than from the source. A program that asks for the private helper by name is rejected at compile time, which is the protection working.

shapes_use.f90
program shapes_use
    use iso_fortran_env, only: real64
    use shapes
    implicit none

    print '(a, f0.4)', 'the area of a unit circle: ', circle_area(1.0_real64)
    print '(a, f0.4)', 'the area of radius 2.5:    ', circle_area(2.5_real64)
end program shapes_use

The good program prints the two areas, 3.1416 and 19.6350. The bad one imports square, which exists in the module but is private, and the compiler reports that the name is not in the module; the message does not reveal whether the routine exists, which is exactly the information hiding that private is for.

shapes_bad.f90
program shapes_bad
    use iso_fortran_env, only: real64
    use shapes, only: square
    implicit none

    print '(a, f0.4)', 'the square of 3: ', square(3.0_real64)
end program shapes_bad

The transcript builds the module with its user, then tries the bad user and fails. Note that the module is compiled in the same command as its client here for brevity; a larger project compiles it once, as the compiling and debugging article shows.

$ gfortran -std=f2018 -Wall -Wextra -fcheck=all shapes.f90 shapes_use.f90 -o shapes_use
$ ./shapes_use
the area of a unit circle: 3.1416
the area of radius 2.5:    19.6350
$ gfortran -std=f2018 -Wall -Wextra shapes.f90 shapes_bad.f90 -o shapes_bad
shapes_bad.f90:3:21:

    3 |     use shapes, only: square
      |                     1
Error: Symbol ‘square’ referenced at (1) not found in module ‘shapes’
shapes_bad.f90:6:43:

    6 |     print '(a, f0.4)', 'the square of 3: ', square(3.0_real64)
      |                                           1
Error: Function ‘square’ at (1) has no IMPLICIT type

Line length and the shape of an expression

A free-form line may hold at most 132 characters, and gfortran treats a longer one as an error because truncating it would change the meaning of the statement. The program below adds twenty-two numbers on one line of 154 characters, which the compiler refuses; the same expression broken after a + with a trailing ampersand compiles and gives the expected 253.

long_bad.f90
program long_bad
    implicit none
    real :: total

    total = 1.0 + 2.0 + 3.0 + 4.0 + 5.0 + 6.0 + 7.0 + 8.0 + 9.0 + 10.0 + 11.0 + 12.0 + 13.0 + 14.0 + 15.0 + 16.0 + 17.0 + 18.0 + 19.0 + 20.0 + 21.0 + 22.0
    print '(a, f0.1)', 'the total: ', total
end program long_bad

The error names the column where the line was cut, and the message -Werror=line-truncation shows that the compiler escalates this warning by default, precisely because a silently truncated expression is a wrong answer. The -ffree-line-length-none option removes the limit, which is a poor idea for shared code: the limit is what keeps a line printable and reviewable.

long_ok.f90
program long_ok
    implicit none
    real :: total

    total = 1.0 + 2.0 + 3.0 + 4.0 + 5.0 + 6.0 + 7.0 + 8.0 + 9.0 + &
        10.0 + 11.0 + 12.0 + 13.0 + 14.0 + 15.0 + 16.0 + 17.0 + &
        18.0 + 19.0 + 20.0 + 21.0 + 22.0
    print '(a, f0.1)', 'the total: ', total
end program long_ok

The continuation lines are indented beyond the start of the expression, so the shape of the sum is visible at a glance. The same rule applies to argument lists, format strings and long conditions: break at an operator, put the ampersand at the end, and align the next line with the expression it continues.

$ gfortran -std=f2018 -Wall -Wextra long_bad.f90 -o long_bad
long_bad.f90:5:132:

    5 |     total = 1.0 + 2.0 + 3.0 + 4.0 + 5.0 + 6.0 + 7.0 + 8.0 + 9.0 + 10.0 + 11.0 + 12.0 + 13.0 + 14.0 + 15.0 + 16.0 + 17.0 + 18.0 + 19.0 + 20.0 + 21.0 + 22.0
      |                                                                                                                                    1
Error: Line truncated at (1) [-Werror=line-truncation]
f951: some warnings being treated as errors
$ gfortran -std=f2018 -Wall -Wextra -fcheck=all long_ok.f90 -o long_ok
$ ./long_ok
the total: 253.0

A checklist for a new file

The rules above fit on one page, and most of them are checked by the compiler rather than by a reviewer. The table collects them with the defect each one prevents, in the order in which a new program unit should be written.

RulePrevents
implicit none in every unita misspelling becoming a new variable
declarations with explicit kindssilent loss of precision
intent on every dummya routine changing what it promised to read
private by default in modulescallers depending on internals
named constants instead of literalsthe same value written twice and changed once
one statement per line, under 132 characterstruncation and unreadable diffs
descriptive names, short comments for the whya reader reconstructing the intent
no obsolescent features in new codea rewrite forced by the next standard

The list is short enough to apply from the first line, which is when it is cheapest. A file that follows it compiles with -Wall -Wextra without a word, and the compiler becomes an ally rather than a formality.

Next steps

The compiling and debugging article explains the flags that enforce these rules, and the unit testing article shows how the promises of a module are recorded as tests. The variables article covers declarations and implicit none, the data types article covers kinds, the modules article explains the private and public lists, and the FORTRAN 77 article shows the habits that the last rule asks you to leave behind.

Source

Fortran 2018 standard (J3/18-007r1)

GNU Fortran manual: Error and Warning Options

Fortran-lang: best practices

In this article we have compared names chosen for a compiler with names chosen for a reader, watched a misspelling become a real variable and then a compile error once implicit none was added, seen an intent(in) assignment rejected and replaced with intent(inout), measured the one percent error of a single-precision sum against the double-precision one, hidden a module helper behind private and watched a caller fail to reach it, broken a 154-character expression at the limit of the line, and collected the rules into a checklist. The previous article, Unit testing in Fortran, turns the same habits into executable checks.

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.