Modules are always the right thing to use 😉
If you have a very simple F90 program you can include functions and subroutines in the ‘contains’ block:
program simple
implicit none
integer :: x, y
x = ...
y = myfunc(x)
contains
function myfunc(x) result(y)
implicit none
integer, intent(in) :: x
integer :: y
...
end function myfunc
end program
Then the interface of the functions/subroutines will be known in the program and don’t need to be defined in an interface block.
For more complex programs you should keep all functions/subroutines in modules and load them when required. So you don’t need to define interfaces, either:
module mymod
implicit none
private
public :: myfunc
contains
function myfunc(x) result(y)
implicit none
integer, intent(in) :: x
integer :: y
...
end function myfunc
end module mymod
program advanced
use mymod, only: myfunc
implicit none
integer :: x, y
x = ...
y = myfunc(x)
end program advanced
The module and the program can (actually should) be in separate files, but the module has to be compiled before the actual program.