Fortran derived types
last modified November 14, 2026
Parallel arrays, one for the names and one for the scores, work until there are five columns and a function has to remember which index belongs to which. A derived type is Fortran's way to define a new type by grouping named variables, called components, into one value. A variable of that type then travels as a single item, can be copied with one assignment, and can be passed to a procedure without listing its parts. This article defines types with components and defaults, builds values with the structure constructor, nests one type inside another, keeps arrays of them, passes them to functions, and gives a type arrays and strings whose size is decided at run time.
Defining a type
A type definition sits between type :: name and
end type, and each component is declared inside it like an
ordinary variable. Variables of the type are then declared with
type(name) :: variable, and a component is reached with the
percent sign, as in p%x. The structure constructor
point(1.0, 2.5) builds a value from the components in the order in
which they were declared, which is the shortest way to fill a whole variable.
program d1
implicit none
type :: point
real :: x, y
end type point
type(point) :: p, q
p = point(1.0, 2.5)
q%x = -3.0
q%y = 0.5
print *, 'p:', p
print *, 'q:', q
print *, 'p%x =', p%x, ' p%y =', p%y
end program d1
The constructor fills both components of p at once, while
q is filled one component at a time. Printing a whole derived type
is allowed when the type is simple, and the runtime writes the components in
declaration order, separated by blanks, which is why p: is followed
by the two numbers. The type definition is local to the program here; a type
that several procedures share lives in a module, as the examples with functions
below show.
$ gfortran d1.f90 -o d1 $ ./d1 p: 1.00000000 2.50000000 q: -3.00000000 0.500000000 p%x = 1.00000000 p%y = 2.50000000
Default values
A component may be given a default value in its declaration, and that value is used whenever a variable of the type is created without one. Defaults turn a type into a safe starting point: a fresh variable already has sensible contents instead of undefined bits. The constructor can still override every default, and a component can be changed later like any other variable. The program below shows a type with four defaults, a fully specified constructor, and one component changed after construction.
program d2
implicit none
type :: particle
character(len=10) :: name = 'unknown'
real :: mass = 1.0
real :: charge = 0.0
logical :: stable = .true.
end type particle
type(particle) :: a, b
print *, 'default: ', a
b = particle('electron', 9.11e-31, -1.0, .true.)
print *, 'b: ', b%name, b%mass, b%charge, b%stable
b%stable = .false.
print *, 'changed: ', b%stable
end program d2
The default variable prints as unknown, a mass of one, a charge of
zero and a stable flag of true, so no component was left undefined. The
constructor then replaces all four, and the assignment to
b%stable changes one component of an existing value. The name
prints with trailing blanks because it was declared with ten characters; the
logical value prints as T or F in list-directed
output.
$ gfortran d2.f90 -o d2 $ ./d2 default: unknown 1.00000000 0.00000000 T b: electron 9.11000034E-31 -1.00000000 T changed: F
Nested types
A component can itself have a derived type, which is how a rectangle is built
from two corners. The constructor of the outer type then takes constructor
calls as arguments, as in
rectangle(point(1.0, 2.0), point(4.0, 6.0), 'window'), and the
components of the inner value are reached with a second percent sign:
r%hi%x is the x coordinate of the upper corner. This nesting can
go as deep as the problem needs.
program d3
implicit none
type :: point
real :: x = 0.0, y = 0.0
end type point
type :: rectangle
type(point) :: lo, hi
character(len=8) :: label = 'box'
end type rectangle
type(rectangle) :: r
real :: area
r = rectangle(point(1.0, 2.0), point(4.0, 6.0), 'window')
area = (r%hi%x - r%lo%x) * (r%hi%y - r%lo%y)
print *, r%label
print *, 'corner:', r%lo%x, r%lo%y, 'to', r%hi%x, r%hi%y
print *, 'area: ', area
end program d3
The lower corner is (1, 2) and the upper one is
(4, 6), so the sides are three and four and the area is twelve.
The label prints with its padding, which is why the line ends after a few
blanks. Building the rectangle with the constructor assigns both nested
point values in one statement, and the arithmetic reads the
coordinates directly through the chain of percent signs.
$ gfortran d3.f90 -o d3 $ ./d3 window corner: 1.00000000 2.00000000 to 4.00000000 6.00000000 area: 12.0000000
Arrays of a derived type
An array of a derived type is declared like any other array, and a constructor
fills it with one value per element. Whole-array operations work on the array
itself, but a component needs the percent sign after the array name:
s%score is an array of integers, so sum(s%score)
adds every score without a loop. Swapping two elements moves all their
components together, which is the reason to group them in the first place.
program d4
implicit none
type :: student
character(len=12) :: name
integer :: score
end type student
type(student) :: s(4), tmp
integer :: i, j
s = [student('alice', 91), student('bob', 78), &
student('carol', 84), student('dave', 67)]
do i = 1, 3
do j = i + 1, 4
if (s(j)%score > s(i)%score) then
tmp = s(i)
s(i) = s(j)
s(j) = tmp
end if
end do
end do
do i = 1, 4
print *, i, s(i)%name, s(i)%score
end do
print *, 'average:', sum(s%score) / real(size(s))
end program d4
The sort puts the scores in descending order and carries the names with them,
so alice and her 91 stay together. The temporary tmp is a whole
student, not a name and a number, which makes the swap three assignments
instead of five. The average is computed from the component array and converted
to real before the division, because dividing an integer sum by an integer size
would truncate the result.
$ gfortran d4.f90 -o d4
$ ./d4
1 alice 91
2 carol 84
3 bob 78
4 dave 67
average: 80.0000000
Types in procedures
A type defined in a module is visible to every procedure that uses the module,
so it can appear in a procedure's argument list. A dummy argument declared
type(point), intent(in) promises not to change the value, while
intent(inout) allows the procedure to modify it. The functions
below also return a value of the type, which is how a midpoint is naturally
expressed.
module geom
implicit none
type, public :: point
real :: x = 0.0, y = 0.0
end type point
contains
pure function distance(a, b) result(d)
type(point), intent(in) :: a, b
real :: d
d = sqrt((b%x - a%x)**2 + (b%y - a%y)**2)
end function distance
pure function midpoint(a, b) result(m)
type(point), intent(in) :: a, b
type(point) :: m
m = point((a%x + b%x) / 2.0, (a%y + b%y) / 2.0)
end function midpoint
subroutine shift(p, dx, dy)
type(point), intent(inout) :: p
real, intent(in) :: dx, dy
p%x = p%x + dx
p%y = p%y + dy
end subroutine shift
end module geom
program d5
use geom
implicit none
type(point) :: a, b, m
a = point(0.0, 0.0)
b = point(3.0, 4.0)
print *, 'distance:', distance(a, b)
m = midpoint(a, b)
print *, 'midpoint:', m%x, m%y
call shift(b, -1.0, 1.0)
print *, 'shifted: ', b%x, b%y
print *, 'new dist:', distance(a, b)
end program d5
The distance from the origin to (3, 4) is 5, the midpoint is
(1.5, 2), and shifting the second point to (2, 5)
changes the distance to about 5.385. The module gives every procedure an
explicit interface, so the compiler checks the number and the types of the
arguments, including the derived type. shift works in place
because its argument is intent(inout); the two functions leave
their arguments alone and return new values.
$ gfortran d5.f90 -o d5 $ ./d5 distance: 5.00000000 midpoint: 1.50000000 2.00000000 shifted: 2.00000000 5.00000000 new dist: 5.38516474
Allocatable components
A polygon has a number of vertices that is not known when the type is defined,
so its coordinate arrays are declared with allocatable. Assigning
an array constructor to tri%x allocates it automatically with the
right size, and allocated() reports whether a component currently
holds storage. The arrays are deallocated with the type when the variable goes
out of scope, but a program that replaces them many times can free them
explicitly to keep the memory use predictable.
program d6
implicit none
type :: polygon
character(len=12) :: name = 'polygon'
real, allocatable :: x(:), y(:)
end type polygon
type(polygon) :: tri
real :: perimeter
integer :: i, n
tri%name = 'triangle'
tri%x = [0.0, 4.0, 0.0]
tri%y = [0.0, 0.0, 3.0]
n = size(tri%x)
print *, tri%name, 'has', n, 'vertices'
print *, 'x:', tri%x
print *, 'y:', tri%y
perimeter = 0.0
do i = 1, n - 1
perimeter = perimeter + &
sqrt((tri%x(i + 1) - tri%x(i))**2 + (tri%y(i + 1) - tri%y(i))**2)
end do
perimeter = perimeter + sqrt((tri%x(1) - tri%x(n))**2 + (tri%y(1) - tri%y(n))**2)
print *, 'perimeter:', perimeter
print *, 'allocated: ', allocated(tri%x)
deallocate (tri%x, tri%y)
print *, 'allocated: ', allocated(tri%x)
end program d6
The triangle has three vertices, and its sides are four, three and five, so the
perimeter is twelve. The loop adds the distance from each vertex to the next
one and stops at n - 1, because the last segment back to the first
vertex would otherwise read past the end of the array; that closing segment is
added after the loop. The two allocated lines show the component
holding storage and then releasing it, which is the same lifecycle as a
standalone allocatable array.
$ gfortran d6.f90 -o d6 $ ./d6 triangle has 3 vertices x: 0.00000000 4.00000000 0.00000000 y: 0.00000000 0.00000000 3.00000000 perimeter: 12.0000000 allocated: T allocated: F
Deferred-length components
A character component can also have a length chosen at run time, with
character(len=:), allocatable. The component then stores exactly
the characters assigned to it, with no padding, which is the right choice for
names read from a file. One restriction matters here: a list-directed read
cannot target an unallocated deferred-length component, so the program reads
into an ordinary fixed-length variable first and then assigns the trimmed text
to the component.
program d7
implicit none
type :: person
character(len=:), allocatable :: name
integer :: year
end type person
type(person), allocatable :: people(:)
type(person) :: p
character(len=80) :: line
character(len=30) :: name
integer :: u, ios, n
open (newunit=u, file='people.txt', status='replace')
write (u, '(a)') 'Ada 1815'
write (u, '(a)') 'Grace 1906'
write (u, '(a)') 'John 1924'
close (u)
allocate (people(0))
open (newunit=u, file='people.txt', status='old')
do
read (u, '(a)', iostat=ios) line
if (ios /= 0) exit
read (line, *, iostat=ios) name, p%year
if (ios /= 0) cycle
p%name = trim(name)
people = [people, p]
end do
close (u)
print *, 'records:', size(people)
do n = 1, size(people)
print *, people(n)%year, people(n)%name
end do
print *, 'lengths:', [(len(people(n)%name), n = 1, size(people))]
end program d7
Three records are read, and the lengths of the stored names are 3, 5 and 4,
which shows that each component was allocated with exactly the number of
letters assigned to it. The array of people grows by one element per record,
and each new element carries its own allocatable component, so the strings are
copied along with the rest of the value. Appending many records this way copies
the whole array each time; a move_alloc or a two-pass count avoids
that for large files.
$ gfortran d7.f90 -o d7
$ ./d7
records: 3
1815 Ada
1906 Grace
1924 John
lengths: 3 5 4
Next steps
A type becomes much more powerful when its procedures are declared inside it:
the type-bound procedures article
gives a type its own methods, the
inheritance and polymorphism article extends
one type from another and selects among the variants at run time, and the
operator overloading article
teaches a type to use + and = like a built-in value.
The arrays article explains the constructors used
to fill arrays of types, and the CSV and text
data article replaces parallel arrays with the records defined here.
Source
Fortran 2018 standard (J3/18-007r1)
Fortran-lang: learning resources
In this article we have defined types with components and defaults, built values with structure constructors, nested one type in another, kept arrays of records, passed types to functions and subroutines, and given a type allocatable arrays and deferred-length strings. The previous article, CSV and text data in Fortran, uses parallel arrays for the same job and shows why grouping the columns is the next step.
Author
List all Fortran tutorials.