ZetCode

Fortran through the standards

last modified November 14, 2026

Fortran has been standardized eight times since 1966, and each revision left the previous one almost intact. That is why a subroutine written for a 1970s mainframe can still be compiled today, and why a reader who learns the current language can also read the old code. The differences between the eras are easiest to see side by side, when one program is written once for each revision.

The program below prints the squares and the cubes of the numbers from one to five, and then the sum of the squares. It is small enough to be written in a page and rich enough to use a loop, an array, arithmetic and formatted output, which are the parts of the language that the revisions changed most often. The five versions print exactly the same six lines, and each is compiled with the flag of its own era where the compiler has one.

The task and the ground rules

The output has five numbered rows and a total: for every i from one to five, the value, its square and its cube, and then the word SUM followed by 55, the sum of the squares. Every version uses the same format, I5 for the number and 2I8 for its two powers, so the alignment is identical and a difference between the versions is a difference in the language, not in the layout.

gfortran has no -std=f66 or -std=f77 option; the oldest standard it knows is -std=f95. For the two oldest programs the flag is therefore -std=legacy, which accepts everything the compiler has ever understood, and the section for each program also shows what a later flag does with the same source. That comparison is the practical summary of what each revision changed.

FORTRAN 66: implicit typing and labelled loops

The oldest version declares no type for any variable. A name that begins with I, J, K, L, M or N is an integer and every other name is a real, so ISQ and ISUM are integers by their first letter. The only declaration is DIMENSION, which gives the array its size. A loop ends at a labelled statement, the input and output formats live in FORMAT statements that the WRITE refers to by label, and the program ends with STOP.

squares66.f
      DIMENSION ISQ(5)
      ISUM = 0
      DO 10 I = 1, 5
         ISQ(I) = I*I
         WRITE (6, 20) I, ISQ(I), ISQ(I)*I
         ISUM = ISUM + ISQ(I)
   10 CONTINUE
   20 FORMAT (I5, 2I8)
      WRITE (6, 30) ISUM
   30 FORMAT ('SUM', I6)
      STOP
      END

The source is in fixed form: the label 10 sits in columns one to five, the statement starts in column seven, and the file name ends in .f so that the compiler reads it that way. The program has no PROGRAM statement, because the standard of 1966 did not require one; the compiler treats the first statement as the start of the main program. Under -std=legacy it compiles without a word, and under -std=f95 it also compiles silently, because nothing in it was ever deleted from the language.

The 2018 flag is the interesting one: the labelled DO is still legal but obsolescent, and gfortran says so. That is the whole story of Fortran in one command, a 1966 program that still runs and a 2018 compiler that recommends replacing one of its statements.

$ gfortran -std=legacy -Wall -Wextra squares66.f -o squares66
$ ./squares66
    1       1       1
    2       4       8
    3       9      27
    4      16      64
    5      25     125
SUM    55
$ gfortran -std=f95 -Wall squares66.f -o squares66_f95
$ gfortran -std=f2018 -Wall squares66.f -o squares66_f2018
squares66.f:3:72:

    3 |       DO 10 I = 1, 5
      |                                                                        1
Warning: Fortran 2018 obsolescent feature: Labeled DO statement at (1)

FORTRAN 77: character data and named constants

The 1977 revision added the PROGRAM statement, the PARAMETER statement for named constants, block IF, and the CHARACTER type, which is the first way to store text that was not a Hollerith constant. The program now names its upper bound, stores the label of the last line in a character variable, and writes to the unit *, which means the default output, instead of to the number six.

squares77.f
      PROGRAM SQUARES
      INTEGER N
      PARAMETER (N = 5)
      INTEGER I, ISQ, ISUM
      CHARACTER*3 LABEL
      LABEL = 'SUM'
      ISUM = 0
      DO 10 I = 1, N
         ISQ = I*I
         WRITE (*, 20) I, ISQ, ISQ*I
         ISUM = ISUM + ISQ
   10 CONTINUE
   20 FORMAT (I5, 2I8)
      WRITE (*, 30) LABEL, ISUM
   30 FORMAT (A3, I6)
      END

The loop is still labelled and still ends at a CONTINUE, because END DO did not arrive until 1990. The declaration CHARACTER*3 is the old way of writing a length, which the next standard replaced with CHARACTER(LEN=3) and then marked obsolescent, so the 1995 flag warns about it while the legacy flag does not.

The warning is worth seeing rather than avoiding: it is the compiler telling a reader which line the 1990 revision would have written differently. The output of the program is byte for byte the output of the 1966 one, because A3 and I6 produce the same characters as the literal 'SUM' and I6 did.

$ gfortran -std=legacy -Wall -Wextra squares77.f -o squares77
$ ./squares77
    1       1       1
    2       4       8
    3       9      27
    4      16      64
    5      25     125
SUM    55
$ gfortran -std=f95 -Wall squares77.f -o squares77_f95
squares77.f:5:18:

    5 |       CHARACTER*3 LABEL
      |                  1
Warning: Obsolescent feature: Old-style character length at (1)

Fortran 90: free form, implicit none and array syntax

The 1990 revision was the largest change in the language's history. Source lines no longer had columns, a line could be up to 132 characters and continue with an ampersand, and the file extension became .f90. implicit none turned an undeclared name into an error, :: separated the attributes from the names, and END DO replaced the label at the end of a loop. The program now declares everything, uses PRINT with a format string, and computes the total with the array intrinsic sum instead of adding inside the loop.

squares90.f90
program squares90
    implicit none
    integer, parameter :: n = 5
    integer :: i, squares(n)

    do i = 1, n
        squares(i) = i * i
    end do
    do i = 1, n
        print '(i5, 2i8)', i, squares(i), squares(i) * i
    end do
    print '(a, i6)', 'SUM', sum(squares)
end program squares90

Fortran 90 programs compile under -std=f95 without a diagnostic, because 95 was a small revision that added PURE, ELEMENTAL and FORALL to what 90 had introduced. The same standard also brought modules, array sections, allocatable arrays, pointers, derived types and the kind mechanism, none of which this short program needs, and every one of which a later article of this series uses.

The total is computed after the loop, from the array, which is the habit that distinguishes the modern style from the old one: the loop fills the array and the intrinsic reduces it, instead of one loop doing both jobs and hiding the reduction inside its body.

$ gfortran -std=f95 -Wall -Wextra -fcheck=all squares90.f90 -o squares90
$ ./squares90
    1       1       1
    2       4       8
    3       9      27
    4      16      64
    5      25     125
SUM    55

Fortran 2003: derived types and type-bound procedures

The 2003 revision brought object orientation and interoperability with C. A derived type can contain procedures, a dummy argument declared class(entry) accepts the type or anything that extends it, and a module can decide what the rest of the program is allowed to see. The program below turns one row of the table into a type with three components and a show method, stores the rows in an allocatable array, and calls the method on each element.

squares03.f90
module table_mod
    implicit none
    private
    public :: entry, make_entry

    type :: entry
        integer :: base = 0
        integer :: square = 0
        integer :: cube = 0
    contains
        procedure :: show => show_entry
    end type entry
contains
    pure function make_entry(i) result(e)
        integer, intent(in) :: i
        type(entry) :: e

        e%base = i
        e%square = i * i
        e%cube = e%square * i
    end function make_entry

    subroutine show_entry(this)
        class(entry), intent(in) :: this

        print '(i5, 2i8)', this%base, this%square, this%cube
    end subroutine show_entry
end module table_mod

program squares03
    use table_mod
    implicit none
    integer, parameter :: n = 5
    type(entry), allocatable :: rows(:)
    integer :: i, total

    allocate (rows(n))
    do i = 1, n
        rows(i) = make_entry(i)
    end do
    total = 0
    do i = 1, n
        call rows(i)%show()
        total = total + rows(i)%square
    end do
    print '(a, i6)', 'SUM', total
end program squares03

The function make_entry builds a value of the type. The module exports the type and the constructor and keeps the subroutine private, yet the type-bound call still reaches it, because the binding belongs to the type. The call rows(i)%show() is a type-bound call: the compiler looks up show in the type, finds show_entry, and passes the element as its first argument. The same revision added allocatable components, C interoperability, PROTECTED, stream input and output, and the ISO_FORTRAN_ENV module.

Compiled with -std=f95 the same source is rejected, and the first message names the construct that the older standard does not have: a CONTAINS section inside a type definition. The second one names the procedure binding. The run is limited to two errors so that the transcript stays short; without the limit the compiler would go on to the class declaration and finally complain that the module file was never written, which is a consequence of the first error.

$ gfortran -std=f2003 -Wall -Wextra -fcheck=all squares03.f90 -o squares03
$ ./squares03
    1       1       1
    2       4       8
    3       9      27
    4      16      64
    5      25     125
SUM    55
$ gfortran -std=f95 -Wall -fmax-errors=2 squares03.f90 -o squares03_f95
squares03.f90:10:12:

   10 |     contains
      |            1
Error: Fortran 2003: CONTAINS block in derived type definition at (1)
squares03.f90:11:39:

   11 |         procedure :: show => show_entry
      |                                       1
Error: Fortran 2003: PROCEDURE statement at (1)
compilation terminated due to -fmax-errors=2.

Fortran 2018: do concurrent and the implicit none list

The last version uses the two features that the previous article examined. implicit none (type, external) requires every name and every external procedure to be declared, and do concurrent states that the iterations of the loop are independent, which allows a compiler to reorder or parallelize them. The loop that fills the array is the natural place for the construct, since each iteration writes one element and reads nothing else.

squares18.f90
program squares18
    implicit none (type, external)
    integer, parameter :: n = 5
    integer :: i, squares(n)

    do concurrent (i = 1:n)
        squares(i) = i * i
    end do
    do i = 1, n
        print '(i5, 2i8)', i, squares(i), squares(i) * i
    end do
    print '(a, i6)', 'SUM', sum(squares)
end program squares18

The printing loop stays an ordinary do, because the output lines must appear in order, and the total is again an array intrinsic. Under -std=f2008 the compiler stops at the implicit none list, the only construct in the program that the 2008 revision does not have; the do concurrent itself is a 2008 feature and would be accepted. Fortran 2018 also added the assumed-rank arrays, the collectives, the events and the error stop values described in the previous article.

Five programs, five eras, one output. The distance between the first and the last is not only syntax: the 1966 program cannot be read without knowing the implicit typing rule, while the 2018 program states every type and every promise, and a compiler checks them.

$ gfortran -std=f2018 -Wall -Wextra -fcheck=all squares18.f90 -o squares18
$ ./squares18
    1       1       1
    2       4       8
    3       9      27
    4      16      64
    5      25     125
SUM    55
$ gfortran -std=f2008 -Wall squares18.f90 -o squares18_f2008
squares18.f90:2:20:

    2 |     implicit none (type, external)
      |                    1
Error: Fortran 2018: IMPLICIT NONE with spec list at (1)

The revisions in one table

The table lists the standards in the order in which they were published, with the year of publication and the change that a reader is most likely to meet. The year in the name is not always the year of the document: Fortran 90 was published in 1991, Fortran 95 in 1997, Fortran 2003 in 2004 and Fortran 2008 in 2010, while the last two appeared in the year they are named after.

RevisionPublishedMain additions
FORTRAN 661966the first standard: fixed form, implicit typing, labelled DO, FORMAT, COMMON
FORTRAN 771978CHARACTER, block IF, PARAMETER, PROGRAM, OPEN and CLOSE
Fortran 901991free form, modules, implicit none, END DO, array syntax, allocatable arrays, derived types, kinds
Fortran 951997PURE, ELEMENTAL, FORALL, more intrinsic functions
Fortran 20032004type-bound procedures, CLASS, allocatable components, C interoperability, stream I/O
Fortran 20082010coarrays, DO CONCURRENT, submodules, ERROR STOP, the BLOCK construct
Fortran 20182018teams, collective subroutines, events, assumed rank, implicit none lists, error stop values
Fortran 20232023conditional expressions, tokenize, selected_logical_kind, reduction in do concurrent

The table explains most of the code a reader will meet in the wild: programs written before 1991 are in fixed form with labels, and programs written after it are in free form with END DO. Everything else is a detail that the compiler will point out when a flag is set. The old code is not wrong, and the compilers still accept it, which is why the language has survived for as long as it has.

Next steps

The what is new article takes the last two revisions apart feature by feature and records which of them the installed compiler accepts, and the FORTRAN 77 article goes through the fixed-form language in detail, including the constructs that this program did not need. The program structure article explains the free-form layout, the modules article covers the unit that the 2003 version uses, and the derived types article explains the type with a method. The compiling and debugging article explains the -std= flag in the context of a build.

Source

ANSI X3.9-1978: FORTRAN 77

Fortran 2018 standard (J3/18-007r1)

John Reid: The new features of Fortran 2018

GNU Fortran manual: Fortran Dialect Options

In this article we have written one small table program five times: with implicit typing, a labelled loop and FORMAT statements in the style of FORTRAN 66; with CHARACTER, PARAMETER and PROGRAM in the style of FORTRAN 77; in free form with implicit none, END DO and sum for Fortran 90; with a module, a derived type, a type-bound procedure and an allocatable array for Fortran 2003; and with do concurrent and the implicit none list for Fortran 2018. Every version prints the same six lines, and the compiler messages show which flag accepts which era. The previous article, What is new in Fortran 2018 and 2023, finishes the history with the features of the last two revisions.

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.