Posted onEdited onInProgramming LanguagesViews: Word count in article: 1.1kReading time ≈5 mins.
The same trivial algorithm in ten languages, ordered by year of birth. Beyond “syntax differs,” the interesting part is what each language insists you write down: type signatures, memory ownership, package envelopes, entry-point ceremony. The shape of the boilerplate is the language’s taste.
for (int i = 2; i <= n; i++) { int c = a + b; a = b; b = c; } return b; }
intmain(int argc, char *argv[]) { int n = atoi(argv[1]); printf("fib(%d) = %d\n", n, fib(n)); return0; }
1 2 3
gcc fibonacci.c ./a.out 1 # fib(1) = 1
C asks little of the programmer up front: no module system, no type inference to fight, no runtime to satisfy. In return it gives you int (overflow at fib(46)), manual argv parsing, and atoi with no error path. The minimalism is real, and so is the cost.
Scheme (1975)
1 2 3 4 5 6
(define (fib n) (define (iter a b n) (if (<= n 1) b (iter b (+ a b) (- n 1)))) (iter01 n))
1 2
racket -f fibonacci.scm -e "(fib 2)" # 1
Tail recursion replaces the loop entirely; no for, no mutable accumulator. Integer arithmetic is bignum by default, so fib(1000) returns the right answer without a library.
procedurefibonacciis N : Integer := Integer'Value(Ada.Command_Line.Argument(Number => 1)); functionfib( N: in Integer) returnIntegeris A : Integer := 0; B : Integer := 1; C : Integer := 1; begin if (N <= 1) then return N; endif; for i in2 .. N loop C := A + B; A := B; B := C; endloop; return B; end; begin Ada.Text_IO.Put_Line("fib(" & Integer'Image(N) & ") = " & Integer'Image(fib(N))); end fibonacci;
Every parameter has a mode (in, out, in out), every variable has a declared type, every package import is explicit. Even the assignment operator (:=) is different from equality (=), so a typo can’t quietly become a comparison.
Standard ML (1983)
1 2 3 4 5 6 7 8 9 10 11
fun fib n = letfun iter (a, b, n) = if n <= 1then b else iter (b, a + b, n - 1) in iter (0, 1, n) end
val n_str = hd (CommandLine.arguments()) val n = valOf (Int.fromString n_str) val res = fib n val _ = print ("fib(" ^ Int.toString n ^ ") = " ^ Int.toString res) val _ = OS.Process.exit(OS.Process.success)
1 2
sml fibonacci.sml 4 # fib(4) = 3
let-binding and pattern matching stand in for the imperative loop. Type inference fills in everything that isn’t a calling convention. The price: the runtime really does want you to handle Option types (valOf will raise if the argument can’t be parsed).
for (int i = 2; i <= n; i++) { int c = a + b; a = b; b = c; } return b; }
intmain(int argc, char *argv[]){ int n = std::atoi(argv[1]); std::cout << "fib(" << n << ") = " << fib(n) << "\n"; return0; }
1 2 3
g++ fibonacci.cpp ./a.out 5 # fib(5) = 5
Same skeleton as C plus std:: prefixes and stream insertion. (using namespace std; makes the body terser and hides which symbols come from where; the qualified form scales.) Modern C++ would reach for std::format and <charconv>, but the bones haven’t changed in forty years.
Python (1991)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
import sys
deffib(n): if n <= 1: return n
a, b = 0, 1 for _ inrange(n - 1): a, b = b, a + b return b
if __name__ == '__main__': n = int(sys.argv[1]) print(f"fib({n}) = {fib(n)}")
1 2
python3 fibonacci.py 6 # fib(6) = 8
Tuple swap (a, b = b, a + b) replaces the temporary. The if __name__ == '__main__': guard exists because every Python file is both a module and a script, so the entry point has to be opt-in. Integers are arbitrary-precision, so overflow is not a thing.
Code lives inside a class even when there is nothing to encapsulate. The filename and the class name have to match. Other JVM languages spent twenty years deleting this ceremony; Java itself eventually got var (Java 10) and records (Java 14), but the class envelope remains.
JavaScript (1995)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
functionfib(n) { if (n <= 1) { return n; }
let a = 0; let b = 1;
for (let i = 2; i <= n; i++) { const c = a + b; a = b; b = c; } return b; }
const n = process.argv[2]; console.log(`fib(${n}) = ${fib(n)}`);
1 2
node fibonacci.js 8 # fib(8) = 21
No class envelope, no entry point, no type signatures. The discipline JavaScript skips is the same discipline TypeScript was invented to add back; the choice between them is whether you want compile-time errors to find typos or whether you want to ship right now.
Scala (2004)
1 2 3 4 5 6 7 8 9 10 11 12 13
@maindeffibonacci(n: Int): Unit = deffib(n: Int): BigInt = if n <= 1then n else var a: BigInt = 0 var b: BigInt = 1 for _ <- 2 to n do val c = a + b a = b b = c b
println(s"fib($n) = ${fib(n)}")
1 2
scala-cli run fibonacci.scala -- 9 # fib(9) = 34
Scala 3’s @main annotation collapses the historical object Fibonacci { def main(args: Array[String]) ... } boilerplate down to a top-level function. The var loop here exists for cross-language parity; the idiomatic Scala for this problem is a lazy stream, lazy val fibs: LazyList[BigInt] = 0 #:: 1 #:: fibs.zip(fibs.tail).map { (a, b) => a + b }, which is a different post.
for_in2...n { let c = a + b a = b b = c } return b }
let args =CommandLine.arguments guard args.count ==2, let n =Int(args[1]) else { print("Usage: swift fibonacci.swift <number>") exit(1) } print("fib(\(n)) = \(fib(n))")
1 2
swift fibonacci.swift 10 # fib(10) = 55
let vs var is a hard distinction baked into the compiler, not a convention. Argument labels are part of the function signature (_ n: Int opts out of the external label). Optionals in argument parsing have to be unwrapped before use, so the guard is mandatory rather than defensive.