Skip to main content
Fortran, despite being one of the oldest programming languages still in active use, has several common anti-patterns that can lead to performance issues, maintainability problems, and bugs. Here are the most important anti-patterns to avoid when writing Fortran code.
! Anti-pattern: Using GOTO statements
PROGRAM calculate
  IMPLICIT NONE
  INTEGER :: i
  REAL :: sum = 0.0
  
  i = 1
10 IF (i <= 100) THEN
     sum = sum + i
     i = i + 1
     GOTO 10
  END IF
  
  PRINT *, "Sum:", sum
END PROGRAM calculate

! Better approach: Use structured control flow
PROGRAM calculate
  IMPLICIT NONE
  INTEGER :: i
  REAL :: sum = 0.0
  
  DO i = 1, 100
    sum = sum + i
  END DO
  
  PRINT *, "Sum:", sum
END PROGRAM calculate
Avoid using GOTO statements, which make code difficult to understand and maintain. Use structured control flow constructs like DO loops, IF-THEN-ELSE, and SELECT CASE instead.
! Anti-pattern: Not using IMPLICIT NONE
PROGRAM calculate
  ! Variables i and sum are implicitly typed based on first letter
  i = 1
  sum = 0.0
  
  DO i = 1, 100
    sum = sum + i
  END DO
  
  PRINT *, "Sum:", sum
END PROGRAM calculate

! Better approach: Use IMPLICIT NONE
PROGRAM calculate
  IMPLICIT NONE
  INTEGER :: i
  REAL :: sum = 0.0
  
  DO i = 1, 100
    sum = sum + i
  END DO
  
  PRINT *, "Sum:", sum
END PROGRAM calculate
Always use IMPLICIT NONE to disable implicit typing. This helps catch typos and undeclared variables at compile time, making your code more robust.
! Anti-pattern: Using COMMON blocks
PROGRAM main
  IMPLICIT NONE
  COMMON /data/ x, y, z
  REAL :: x, y, z
  
  x = 1.0
  y = 2.0
  z = 3.0
  
  CALL process_data()
END PROGRAM main

SUBROUTINE process_data()
  IMPLICIT NONE
  COMMON /data/ x, y, z
  REAL :: x, y, z
  
  PRINT *, "Data:", x, y, z
END SUBROUTINE process_data

! Better approach: Use modules
MODULE data_module
  IMPLICIT NONE
  REAL :: x, y, z
END MODULE data_module

PROGRAM main
  USE data_module
  IMPLICIT NONE
  
  x = 1.0
  y = 2.0
  z = 3.0
  
  CALL process_data()
END PROGRAM main

SUBROUTINE process_data()
  USE data_module
  IMPLICIT NONE
  
  PRINT *, "Data:", x, y, z
END SUBROUTINE process_data
Avoid using COMMON blocks for sharing data between program units. Use modules instead, which provide better type checking, encapsulation, and maintainability.
! Anti-pattern: Using fixed-form source format
C This is a comment in fixed-form Fortran
      PROGRAM main
      IMPLICIT NONE
      INTEGER i, j
      REAL*8 result
C Initialize variables
      i = 10
      j = 20
      result = DBLE(i) + DBLE(j)
      PRINT *, result
      END PROGRAM main

! Better approach: Use free-form source format
! This is a comment in free-form Fortran
PROGRAM main
  IMPLICIT NONE
  INTEGER :: i, j
  REAL(KIND=8) :: result
  
  ! Initialize variables
  i = 10
  j = 20
  result = REAL(i, KIND=8) + REAL(j, KIND=8)
  PRINT *, result
END PROGRAM main
Use free-form source format (introduced in Fortran 90) instead of the older fixed-form format. Free-form is more readable, flexible, and doesn’t have column restrictions.
! Anti-pattern: Using obsolete features
PROGRAM old_style
  IMPLICIT NONE
  INTEGER :: i
  REAL :: arr(10)
  
  DO 10 i = 1, 10
    arr(i) = i * 2.0
10 CONTINUE
  
  PRINT *, arr
END PROGRAM old_style

! Better approach: Use modern constructs
PROGRAM modern_style
  IMPLICIT NONE
  INTEGER :: i
  REAL :: arr(10)
  
  DO i = 1, 10
    arr(i) = i * 2.0
  END DO
  
  PRINT *, arr
END PROGRAM modern_style
Avoid using obsolete features like labeled DO loops, arithmetic IF statements, and computed GOTO. Use modern control structures that are more readable and maintainable.
! Anti-pattern: Global variables without encapsulation
PROGRAM main
  IMPLICIT NONE
  REAL :: global_data(100)
  
  CALL initialize_data(global_data)
  CALL process_data(global_data)
  CALL output_results(global_data)
END PROGRAM main

! Better approach: Use modules for encapsulation
MODULE data_handling
  IMPLICIT NONE
  PRIVATE  ! All entities are private by default
  PUBLIC :: initialize_data, process_data, output_results
  
  REAL, PRIVATE :: global_data(100)
  
  CONTAINS
    SUBROUTINE initialize_data()
      ! Implementation
    END SUBROUTINE initialize_data
    
    SUBROUTINE process_data()
      ! Implementation
    END SUBROUTINE process_data
    
    SUBROUTINE output_results()
      ! Implementation
    END SUBROUTINE output_results
END MODULE data_handling

PROGRAM main
  USE data_handling
  IMPLICIT NONE
  
  CALL initialize_data()
  CALL process_data()
  CALL output_results()
END PROGRAM main
Use modules to encapsulate data and related procedures. This improves code organization, maintainability, and helps prevent unintended modifications to data.
! Anti-pattern: Using EQUIVALENCE
PROGRAM memory_sharing
  IMPLICIT NONE
  INTEGER :: int_array(10)
  REAL :: real_array(10)
  
  EQUIVALENCE (int_array, real_array)
  
  int_array = 42
  PRINT *, real_array  ! Undefined behavior
END PROGRAM memory_sharing

! Better approach: Use explicit type conversion
PROGRAM type_conversion
  IMPLICIT NONE
  INTEGER :: int_array(10)
  REAL :: real_array(10)
  INTEGER :: i
  
  int_array = 42
  
  DO i = 1, 10
    real_array(i) = REAL(int_array(i))
  END DO
  
  PRINT *, real_array
END PROGRAM type_conversion
Avoid using the EQUIVALENCE statement, which can lead to undefined behavior and makes code hard to understand and maintain. Use explicit type conversions or derived types instead.
! Anti-pattern: Element-by-element operations in loops
PROGRAM array_ops
  IMPLICIT NONE
  INTEGER :: i
  REAL :: a(100), b(100), c(100)
  
  ! Initialize arrays
  DO i = 1, 100
    a(i) = i * 1.0
    b(i) = i * 2.0
  END DO
  
  ! Element-by-element addition
  DO i = 1, 100
    c(i) = a(i) + b(i)
  END DO
  
  PRINT *, SUM(c)
END PROGRAM array_ops

! Better approach: Use array operations
PROGRAM array_ops
  IMPLICIT NONE
  REAL :: a(100), b(100), c(100)
  
  ! Initialize arrays
  a = [(i * 1.0, i = 1, 100)]
  b = [(i * 2.0, i = 1, 100)]
  
  ! Array addition
  c = a + b
  
  PRINT *, SUM(c)
END PROGRAM array_ops
Use Fortran’s array operations instead of explicit loops for element-by-element operations. Array operations are more concise, often more efficient, and can be automatically parallelized by the compiler.
! Anti-pattern: Using fixed-size arrays
SUBROUTINE process_data(n, data)
  IMPLICIT NONE
  INTEGER, INTENT(IN) :: n
  REAL, INTENT(INOUT) :: data(1000)  ! Fixed size, wasteful if n << 1000
  
  ! Process only the first n elements
  data(1:n) = data(1:n) * 2.0
END SUBROUTINE process_data

! Better approach: Use allocatable arrays
SUBROUTINE process_data(n, data)
  IMPLICIT NONE
  INTEGER, INTENT(IN) :: n
  REAL, ALLOCATABLE, INTENT(INOUT) :: data(:)
  
  ! Allocate only what's needed
  IF (.NOT. ALLOCATED(data)) THEN
    ALLOCATE(data(n))
  ELSE IF (SIZE(data) /= n) THEN
    DEALLOCATE(data)
    ALLOCATE(data(n))
  END IF
  
  ! Process all elements
  data = data * 2.0
END SUBROUTINE process_data
Use allocatable arrays instead of fixed-size arrays when the size is not known at compile time. This avoids wasting memory and potential buffer overflows.
! Anti-pattern: Not using INTENT attributes
SUBROUTINE calculate(a, b, result)
  IMPLICIT NONE
  REAL :: a, b, result
  
  result = a * b
END SUBROUTINE calculate

! Better approach: Use INTENT attributes
SUBROUTINE calculate(a, b, result)
  IMPLICIT NONE
  REAL, INTENT(IN) :: a, b
  REAL, INTENT(OUT) :: result
  
  result = a * b
END SUBROUTINE calculate
Always use INTENT attributes (IN, OUT, INOUT) for subroutine and function arguments. This documents how arguments are used, helps prevent bugs, and enables compiler optimizations.
! Anti-pattern: Using ENTRY statement
SUBROUTINE process_data(data, n)
  IMPLICIT NONE
  REAL, INTENT(INOUT) :: data(n)
  INTEGER, INTENT(IN) :: n
  INTEGER :: i
  
  ! Process data
  DO i = 1, n
    data(i) = data(i) * 2.0
  END DO
  
  RETURN
  
  ENTRY normalize_data(data, n)
  ! Normalize data
  data = data / MAXVAL(data)
  
  RETURN
END SUBROUTINE process_data

! Better approach: Use separate subroutines
SUBROUTINE process_data(data, n)
  IMPLICIT NONE
  REAL, INTENT(INOUT) :: data(n)
  INTEGER, INTENT(IN) :: n
  INTEGER :: i
  
  ! Process data
  DO i = 1, n
    data(i) = data(i) * 2.0
  END DO
END SUBROUTINE process_data

SUBROUTINE normalize_data(data, n)
  IMPLICIT NONE
  REAL, INTENT(INOUT) :: data(n)
  INTEGER, INTENT(IN) :: n
  
  ! Normalize data
  data = data / MAXVAL(data)
END SUBROUTINE normalize_data
Avoid using the ENTRY statement, which creates multiple entry points in a single subroutine or function. This makes code hard to understand and maintain. Use separate subroutines or functions instead.
! Anti-pattern: Poor error handling
PROGRAM file_reader
  IMPLICIT NONE
  INTEGER :: unit, ios
  REAL :: data(100)
  
  OPEN(UNIT=10, FILE="data.txt", STATUS="OLD")
  READ(10, *) data
  CLOSE(10)
  
  PRINT *, "Data:", data
END PROGRAM file_reader

! Better approach: Proper error handling
PROGRAM file_reader
  IMPLICIT NONE
  INTEGER :: unit, ios
  REAL :: data(100)
  CHARACTER(LEN=100) :: error_msg
  
  OPEN(NEWUNIT=unit, FILE="data.txt", STATUS="OLD", IOSTAT=ios, IOMSG=error_msg)
  IF (ios /= 0) THEN
    PRINT *, "Error opening file: ", TRIM(error_msg)
    STOP
  END IF
  
  READ(unit, *, IOSTAT=ios, IOMSG=error_msg) data
  IF (ios /= 0) THEN
    PRINT *, "Error reading file: ", TRIM(error_msg)
    CLOSE(unit)
    STOP
  END IF
  
  CLOSE(unit)
  
  PRINT *, "Data:", data
END PROGRAM file_reader
Use proper error handling for I/O operations and other potential sources of errors. Check status codes (IOSTAT) and provide meaningful error messages (IOMSG).
! Anti-pattern: Not using KIND parameters
PROGRAM precision_issues
  IMPLICIT NONE
  REAL :: x = 1.0 / 3.0
  DOUBLE PRECISION :: y = 1.0D0 / 3.0D0
  
  PRINT *, "Single precision:", x
  PRINT *, "Double precision:", y
END PROGRAM precision_issues

! Better approach: Use KIND parameters
PROGRAM precision_control
  IMPLICIT NONE
  INTEGER, PARAMETER :: sp = SELECTED_REAL_KIND(6, 37)
  INTEGER, PARAMETER :: dp = SELECTED_REAL_KIND(15, 307)
  
  REAL(KIND=sp) :: x = 1.0_sp / 3.0_sp
  REAL(KIND=dp) :: y = 1.0_dp / 3.0_dp
  
  PRINT *, "Single precision:", x
  PRINT *, "Double precision:", y
END PROGRAM precision_control
Use KIND parameters to specify precision requirements in a portable way. This makes your code more maintainable and portable across different platforms.
! Anti-pattern: Using hardcoded unit numbers
PROGRAM file_io
  IMPLICIT NONE
  REAL :: data(100)
  
  OPEN(UNIT=7, FILE="input.dat", STATUS="OLD")
  READ(7, *) data
  CLOSE(7)
  
  ! Process data...
  
  OPEN(UNIT=8, FILE="output.dat", STATUS="REPLACE")
  WRITE(8, *) data
  CLOSE(8)
END PROGRAM file_io

! Better approach: Use NEWUNIT and proper file handling
PROGRAM file_io
  IMPLICIT NONE
  INTEGER :: input_unit, output_unit, ios
  REAL :: data(100)
  CHARACTER(LEN=100) :: error_msg
  
  OPEN(NEWUNIT=input_unit, FILE="input.dat", STATUS="OLD", &
       IOSTAT=ios, IOMSG=error_msg)
  IF (ios /= 0) THEN
    PRINT *, "Error opening input file: ", TRIM(error_msg)
    STOP
  END IF
  
  READ(input_unit, *, IOSTAT=ios, IOMSG=error_msg) data
  IF (ios /= 0) THEN
    PRINT *, "Error reading input file: ", TRIM(error_msg)
    CLOSE(input_unit)
    STOP
  END IF
  
  CLOSE(input_unit)
  
  ! Process data...
  
  OPEN(NEWUNIT=output_unit, FILE="output.dat", STATUS="REPLACE", &
       IOSTAT=ios, IOMSG=error_msg)
  IF (ios /= 0) THEN
    PRINT *, "Error opening output file: ", TRIM(error_msg)
    STOP
  END IF
  
  WRITE(output_unit, *, IOSTAT=ios, IOMSG=error_msg) data
  IF (ios /= 0) THEN
    PRINT *, "Error writing output file: ", TRIM(error_msg)
    CLOSE(output_unit)
    STOP
  END IF
  
  CLOSE(output_unit)
END PROGRAM file_io
Use NEWUNIT to get a unique unit number instead of hardcoding unit numbers. Also, always check for I/O errors and handle them appropriately.
! Anti-pattern: Not initializing variables
PROGRAM uninitialized
  IMPLICIT NONE
  INTEGER :: i, sum
  
  DO i = 1, 10
    sum = sum + i  ! sum is uninitialized on first use
  END DO
  
  PRINT *, "Sum:", sum
END PROGRAM uninitialized

! Better approach: Initialize variables
PROGRAM initialized
  IMPLICIT NONE
  INTEGER :: i, sum = 0
  
  DO i = 1, 10
    sum = sum + i
  END DO
  
  PRINT *, "Sum:", sum
END PROGRAM initialized
Always initialize variables before using them. Uninitialized variables can contain unpredictable values, leading to bugs that are hard to track down.
! Anti-pattern: Using separate arrays for related data
PROGRAM particle_simulation
  IMPLICIT NONE
  INTEGER, PARAMETER :: n = 1000
  REAL :: pos_x(n), pos_y(n), pos_z(n)
  REAL :: vel_x(n), vel_y(n), vel_z(n)
  REAL :: mass(n)
  INTEGER :: i
  
  ! Initialize particles
  DO i = 1, n
    pos_x(i) = RANDOM_NUMBER()
    pos_y(i) = RANDOM_NUMBER()
    pos_z(i) = RANDOM_NUMBER()
    vel_x(i) = 0.0
    vel_y(i) = 0.0
    vel_z(i) = 0.0
    mass(i) = 1.0
  END DO
  
  ! Simulate particles...
END PROGRAM particle_simulation

! Better approach: Use derived types
PROGRAM particle_simulation
  IMPLICIT NONE
  
  TYPE :: particle_t
    REAL :: pos(3)  ! Position (x, y, z)
    REAL :: vel(3)  ! Velocity (vx, vy, vz)
    REAL :: mass    ! Mass
  END TYPE particle_t
  
  INTEGER, PARAMETER :: n = 1000
  TYPE(particle_t) :: particles(n)
  INTEGER :: i
  
  ! Initialize particles
  DO i = 1, n
    particles(i)%pos = [RANDOM_NUMBER(), RANDOM_NUMBER(), RANDOM_NUMBER()]
    particles(i)%vel = [0.0, 0.0, 0.0]
    particles(i)%mass = 1.0
  END DO
  
  ! Simulate particles...
END PROGRAM particle_simulation
Use derived types to group related data together. This makes code more organized, readable, and maintainable, especially for complex data structures.
! Anti-pattern: Poor or no documentation
SUBROUTINE calculate_statistics(data, n, mean, stddev)
  IMPLICIT NONE
  INTEGER, INTENT(IN) :: n
  REAL, INTENT(IN) :: data(n)
  REAL, INTENT(OUT) :: mean, stddev
  
  mean = SUM(data) / n
  stddev = SQRT(SUM((data - mean)**2) / n)
END SUBROUTINE calculate_statistics

! Better approach: Proper documentation
!> Calculate the mean and standard deviation of a dataset
!>
!> @param data   Input array of data points
!> @param n      Number of data points
!> @param mean   Output mean value
!> @param stddev Output standard deviation
!>
!> @note This calculates the population standard deviation,
!>       not the sample standard deviation.
SUBROUTINE calculate_statistics(data, n, mean, stddev)
  IMPLICIT NONE
  INTEGER, INTENT(IN) :: n          !< Number of data points
  REAL, INTENT(IN) :: data(n)       !< Input data array
  REAL, INTENT(OUT) :: mean         !< Mean of the data
  REAL, INTENT(OUT) :: stddev       !< Standard deviation
  
  mean = SUM(data) / n
  stddev = SQRT(SUM((data - mean)**2) / n)
END SUBROUTINE calculate_statistics
Document your code with comments that explain the purpose, parameters, return values, and any important details or assumptions. This makes your code more maintainable and easier for others (including your future self) to understand.
! Anti-pattern: No testing or manual testing
PROGRAM main
  IMPLICIT NONE
  REAL :: result
  
  result = calculate_area(3.0, 4.0)
  PRINT *, "Area:", result
  ! Manual check: Is it close to 12.0?
END PROGRAM main

! Better approach: Automated testing
PROGRAM test_calculate_area
  IMPLICIT NONE
  REAL :: result, expected
  REAL, PARAMETER :: tolerance = 1.0E-6
  
  ! Test case 1
  result = calculate_area(3.0, 4.0)
  expected = 12.0
  CALL assert_close(result, expected, tolerance, "Rectangle 3x4")
  
  ! Test case 2
  result = calculate_area(0.0, 5.0)
  expected = 0.0
  CALL assert_close(result, expected, tolerance, "Rectangle 0x5")
  
  PRINT *, "All tests passed!"
  
  CONTAINS
    SUBROUTINE assert_close(actual, expected, tol, message)
      REAL, INTENT(IN) :: actual, expected, tol
      CHARACTER(LEN=*), INTENT(IN) :: message
      
      IF (ABS(actual - expected) > tol) THEN
        PRINT *, "Test failed: ", message
        PRINT *, "  Expected: ", expected
        PRINT *, "  Actual:   ", actual
        STOP 1
      END IF
    END SUBROUTINE assert_close
END PROGRAM test_calculate_area
Write automated tests for your code to verify that it works correctly. This helps catch bugs early and ensures that your code continues to work as expected when you make changes.
I