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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
| Rule | Prevents |
|---|---|
implicit none in every unit | a misspelling becoming a new variable |
| declarations with explicit kinds | silent loss of precision |
intent on every dummy | a routine changing what it promised to read |
private by default in modules | callers depending on internals |
| named constants instead of literals | the same value written twice and changed once |
| one statement per line, under 132 characters | truncation and unreadable diffs |
| descriptive names, short comments for the why | a reader reconstructing the intent |
| no obsolescent features in new code | a 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
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
List all Fortran tutorials.